gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated (9782a22 -> f829235)


From: gnunet
Subject: [taler-merchant] branch master updated (9782a22 -> f829235)
Date: Sun, 05 Jul 2020 20:48:33 +0200

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

grothoff pushed a change to branch master
in repository merchant.

    from 9782a22  removed the GnuTLS check for cURL
     new 773e5a0  first draft of new SQL logic
     new e288299  rework merchant schema
     new 0a327ce  complete first draft of new SQL schema
     new 0b8e550  starting v1 protocol dispatching logic
     new 00a03bf  sketch instance loading
     new bee130c  complete bootstrap logic in new design
     new 49129bb  implement GET /instances
     new ed553c1  support PATCH as well
     new b03f6cf  implement instances_get in library
     new 741f712  implement POST /instances
     new e93a943  implement GET /instances/
     new d5f14ec  start on DELETE instance
     new f0ba12d  implement DELETE /instances/$ID
     new 212298a  implement PATCH
     new 6b93d09  implement GET /products
     new 7a464be  work on delete/get products by ID
     new 3817d2e  implement PATCH handlers
     new 458dc36  implement POST products/lock
     new 64cc6eb  backenddb implementation work
     new 33ad6a9  squealing
     new a01d45c  simplifying SQL
     new 4913f56  work on /products and /instances C API
     new 3577418  specify remaining /instance API
     new a417ca1  implement POST /instances
     new 5107553  finish patch
     new f21f5eb  implement GET /products
     new 70bb114  implement GET /products/
     new 035ceae  implement PATCH /products/
     new fc99580  implement POST /products
     new fffe2bd  implement POST /products//lock
     new 7aaeee0  implemenet DELETE /products/
     new e357b73  move libtalermerchanttesting and test cases to src/testing/
     new 0d68648  move libtalermerchanttesting and test cases to src/testing/
     new 9672f32  add GET /instance CMD
     new 8601f46  add CMD for GET /instances
     new 3469026  add DELETE /instances/ID command
     new 9216538  add cmd for POST /instances
     new a92f8f2  add PATCH /instances/ID command
     new ba903a4  add PATCH /instances/ID command
     new 8dbb0c6  add PATCH /instances/ID command
     new b378eea  add DELETE/PURGE /instances/ID command
     new 0b57eac  implement purge
     new f4b19c0  toward stesting
     new 5db333a  fix ftbfs in lock logic
     new 874f095  baseURL construction helper function'
     new 0fa2888  improve API
     new a376e29  typo
     new 6548cc6  fix tests
     new 88bed1d  fix fmt string
     new 3388954  misc fixes
     new 23750b7  misc. fixes
     new 46aa711  misc bugfixes
     new ff42a36  implemente DELETE /orders/ID
     new 6ca84e4  first hack at POST /orders
     new dcbac9e  fix fTBFS
     new fa640a1  implement POST /orders client with all optional arguments
     new f799df3  implement logic to complete POSTed /orders using inventory 
data
     new fc57ccc  implement GET /orders in libtalermerchant
     new 5152270  sql-ing for GET /orders
     new 68c440b  GET /orders logic
     new 2085386  rename fest to match new structure
     new 7147830  mark as obsolete
     new eef2a64  remove duplicate
     new 255f162  work on POST /orders/ID/claim (unfinished)
     new e3d91bf  db implementation of claiming
     new 5da121e  implement order claiming
     new ce97f3d  work on /pay API revision
     new f5c484e  sql-ing
     new 3f5bc6f  update pay logic
     new 577e344  work on /pay and /abort processing
     new 4921780  work on /abort logic
     new d5a57b8  DB API for /abort
     new d811acb  DB API for /abort
     new 81aa94e  DB API for /abort
     new 9b18406  work on abort
     new 3528056  adapt to exchangev8 protocol
     new dcc083c  POST /orders/ID/refund implementation
     new 74c5fa8  work on refund
     new e6931be  backend code for /refund handling
     new 3493795  implement /refund handling
     new 9e90a44  fix/complete inventory management logic
     new 35623b7  implement abort client lib
     new 4f4def8  renaming
     new 8d88332  dce
     new a4f0dcb  update testing logic for order CMDs
     new 607c8d8  GET /orders/ID API design
     new dbee9e5  revise GET /private/orders API
     new 49a6e2a  fix benchmark FTBFS
     new 2a5388a  fix benchmark FTBFS
     new e138cd0  starting with get-orders-ID logic
     new 9d33dde  implement GET /orders/ID
     new 68fcf5f  fix backenddb
     new 2ba0fed  updates
     new c84339d  fix ftbfs
     new 02faaca  db work for POST /transfers
     new 05afe21  fix FTBFS
     new 1d41f0e  integrate POST /tranfers handler
     new 4344f16  implement POST /transfers
     new 1c14e33  more work on post /transfers and the like
     new d6fc0b6  more work on POST /transfer
     new 6f1bc0a  more SQL
     new 7b3fd48  towards idempotency in POST /private/transfers
     new 47811a6  towards GET /transfers
     new a5da6d3  GET /transfers implementation
     new a0c0df4  API design for /transfers
     new 8919c56  implement merchant_api_post_transfers.c
     new a48af85  implement GET /transfers
     new 9bac37c  sketch for GET /transfers cmd
     new 80f5449  commentfix
     new f4ab3df  fix benchmark FTBFS
     new 9381057  fix link errors in test
     new 3a7f96a  rename _v to _mv for merchant
     new 75855e0  use unregister
     new de6225f  fix sql
     new dc6a6c0  misc. fixes
     new 8e7843a  fix test
     new 1792cba  get simple pay test to pass
     new 49bb51d  implement POST /reserves
     new 7d83b80  better error handling
     new 211a3cb  implement insert_reserve
     new 0767f8f  Merge branch 'master' into protocolV1
     new 39d2fb9  fixed bool casts in plugin initialization
     new 601abc1  added tests for instance creation and lookup
     new 9af93ee  expanding DB API
     new 11b7a96  more work on GET /reserves logic
     new 74913ff  more instance and product related tests
     new 3d6cce7  implement reserve deletion
     new e6cd350  more work on tipping implementation
     new d46bd87  fixed coding style in db tests and double callbacks in 
lookup_instances
     new fe19d9a  towards POST tips pickup impl
     new 709b002  added more instance tests and fixed mem leaks/code style in 
tests
     new a6288a2  Merge branch 'protocolV1' of git+ssh://git.taler.net/merchant 
into protocolV1
     new b21e20f  fix ftbfs
     new b4fc637  finished writing db tests for products
     new 1e6adbf  fix ftbfs
     new a006a45  add GET /tips/ID handler
     new 49157c0  implement POST /tips/ID/pickup
     new 5ee7826  DB prep work
     new 6a2345b  wrote some db tests for orders
     new ad4b487  wrote db tests for accounts
     new 10d311c  Merge branch 'protocolV1' of git+ssh://git.taler.net/merchant 
into protocolV1
     new 0c4ff0e  wip on DB plugin for tip pickup
     new 7c35563  more SQL for tips
     new 892dd11  refactored instance test code
     new 35a94ff  refactored the existing product and order tests
     new e233381  wrote tests for deposits in the db api
     new 8fdaf76  added more tests for contract terms, deposits, and transfers
     new 6b908a8  fixed inventory locking, test code for transfer lookups
     new d809c7b  more db tests for transfers and tips
     new 7a20597  backenddb tests for refunds
     new f1e1ff8  backenddb tests for lookup_transfers, 
lookup_transfer_summary, lookup_transfer_details
     new 6ad48a9  wrote db tests for tips/lookups
     new fffdd0c  fixed test for increase_refund
     new 3e75373  return creation_time instead of leaving it uninitizlied, mark 
problematic missing lines
     new 870d29d  fix ftbfs
     new e0991aa  implement lookup tip details
     new 059797b  fix DB use
     new a043dd9  work on tip APIs
     new 48ea83f  add POST /tips logic
     new 4089ebe  address DB fixes for missing EC values
     new 990f4d4  fix misc issues in client APIs
     new 9154806  implement GET /reserves/RPUB
     new 6f11901  implement filter
     new cd3a94a  fixed db test building and added test for wire fee storage
     new e3c0320  add logic for activation of reserves to merchant backend DB
     new e5bf2ab  SQL fixing
     new 8fb5fd3  implement TALER_MERCHANT_reserves_get()
     new 61582eb  implement GET /reserves/RESERVE_ID
     new f3a9cff  implement POST /private/reserves
     new 2edb7ff  implement POST /private/reserves
     new 5e5fa74  first high-level hack job at GET /orders/ID -- certainly 
FTBFS still
     new 4bd45c1  tests/fixes for tips and pickups in backenddb
     new 2789caa  Merge branch 'protocolV1' of git+ssh://git.taler.net/merchant 
into protocolV1
     new 24a2a3f  new backenddb APIs for GET /private/orders/ID
     new 5941357  avoid amount_zero, use DEFAULT instead
     new 7811904  work on GET /private/orders/ID"
     new 03da63d  cleaned backenddb test code, completed tests for instances & 
products
     new a48cac9  wrote method for delete reserve
     new 96affdc  Merge branch 'protocolV1' of git+ssh://git.taler.net/merchant 
into protocolV1
     new ea5d4f6  more work on private-get-orders-ID
     new 03146ba  more test cleanup and some lookup_orders filter tests
     new ab6ee16  More complete tests for deposits, transfers, refund, lookup 
orders
     new fbd9349  work on getting TransactionWireTransfer reply built
     new fcf6049  work on implementing GET /private/orders/ID
     new b5b3f8a  start to build get-orders-ID
     new cb8cb51  headers and empty source files for GET /private/tips
     new 0c481ef  Merge branch 'protocolV1' of git+ssh://git.taler.net/merchant 
into protocolV1
     new 83bd171  private-get-orders-ID now compiles -- but not more than that
     new 7a9c8b2  created lookup_tips method for backenddb
     new 26d5adb  implementation of GET /private/tips
     new 22b07f7  need #include
     new 115d3fc  test/fix for POST /private/reserves
     new 4836173  Merge branch 'protocolV1' of git+ssh://git.taler.net/merchant 
into protocolV1
     new e006e76  finish FIXMEs in private-get-orders-ID:
     new 3fd4ebf  remark
     new 175162b  do not forget to kick MHD
     new 4778b14  remove from waiting list when resuming
     new 0d4f84d  enable handler
     new d38e116  insert missing functions (unimplemented)
     new 41c9444  test with MHD error
     new ed7142e  fix crash
     new 2c1a850  work on missing functions
     new 853a2c3  sync
     new 3f2b731  moved yna to exchange
     new ae2bfdb  fixed authorize tip and tests for GET /private/tips
     new f041139  more backenddb tests
     new 2f853be  test DELETE /private/reserves/
     new 573ee18  implement missing SQL
     new 1ceb91a  DCE
     new e39aa8b  fix #5957
     new fdf89e8  adjust API to current spec
     new ab22ee3  adjust GET /orders/ID to match spec
     new b8fa0dd  starting with merchant_api_merchant_get_order.c
     new 864a55a  add logic to parse refund details
     new 573a227  this was done by Jonathan
     new 458d1ac  clean up logic: use new EXCHANGE_YNA enum, avoid crazy macro 
construction to build URLs, use full power of TALER_url_join() instead
     new 8716f80  implement merchant_api_merchant_get_order.c
     new 0dc0310  allow refund_delay to be specified with POST /orders, fix 
FTBFS
     new af15831  tests for new db methods
     new 1d22e4f  test for GET /private/reserves
     new 63c035b  test for GET /private/reserves/
     new 06010ba  fix/test for POST tips//pickup
     new 6624576  implementations, tests, and renames for GET /tips/ & GET 
/private/tips/
     new 566408f  stricter tests for tips/reserves
     new e352440  stricter tests for query reserve(s) methods
     new 37bfe14  naive tests for the family of GET order(s) methods
     new 9bae035  implement library method for DELETE /orders/ and tests
     new da5f393  start with reserve processing logic
     new c6a10b5  finish taler-merchant-httpd_reserves implementation
     new 4621e2e  add amount checks
     new 5341e4d  address 413 fixme
     new e5c81bc  413 limits
     new 5d3bfab  add missing notifications to private-get-orders long poller
     new 4893f35  improved backenddb tests
     new d4f400e  Merge branch 'protocolV1' of git+ssh://git.taler.net/merchant 
into protocolV1
     new da6a10e  deeper checks for GET /private/instances/
     new d83304e  fix type conversion warning and test failure for backenddb
     new cac0965  more checks for GET /private/products/
     new 0748ecd  return active-status of reserves from backenddb (fixes FIXMEs)
     new 66f0f2d  Merge branch 'protocolV1' of ssh://git.taler.net/merchant 
into protocolV1
     new ae52368  clarify with/without wire fee issue
     new 3e5a21a  also parse and return timestamp and row_id
     new f8c01f4  implement signature verification on pay response
     new cecafaa  use and handle all /pay status codes
     new 8f52ed6  DCE: removing legacy code and APIs
     new d52356b  implement taler-merchant-setup-reserve CLI tool
     new d34f954  harder tests for GET /private/instances, GET /private/products
     new 4ce3a78  deeper checks on GET /private/orders
     new ebfa788  made a testing trait for order claim nonce
     new 0fbdd6c  got refunds working and tested
     new 9d0a68e  wallet get order handles refunds properly
     new 6bca914  minor style improvements
     new 2bc6e49  fix FIXME
     new 057ac0f  logic to update 'wired' status of an order
     new 60df725  deeper checks for merchant get order
     new 2c0797c  test for auto marking orders as wired after inserting 
sufficient transfers
     new 6e47f75  updated doxygen generation
     new ab27991  excluded doc/doxygen from gitignore
     new cde317f  more docs/thorough checks for backend db & long polling for 
merchant get order
     new 4b820e2  fix #6236
     new c7465ea  removed hardcoded row numbers from backenddb tests
     new a118bd2  deduplicate logic
     new b637514  long polling test for GET /private/orders
     new c8b6ad8  filler max_upload values for all POST/PATCH handlers
     new f641e25  got tips test working again
     new e24dd48  get reserve and tip testing commands use variadic args
     new bd6a7e9  get pay-again and pay-abort working again
     new 2da3c81  improved db tests for transfers
     new dcd5a4c  removed outdated test code
     new 03ca8c1  twister, get tips, instance, and post transfer tests
     new 4b5ceba  some clean up of merchant benchmark code
     new 1610f3a  more merchant benchmark cleanup
     new c4dc3bf  use improved rewind API
     new 33d332a  test GET /private/transfers
     new 74e6cc6  fix compiler warnings for test_merchantdb.c
     new df3b474  avoid allocation
     new f829235  GNUNET_free_non_null -> GNUNET_free rename

The 277 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |   26 +-
 configure.ac                                       |    2 +
 doc/Makefile.am                                    |    2 +
 doc/doxygen/.gitignore                             |    2 +
 doc/doxygen/Makefile.am                            |   18 +
 doc/doxygen/logo.svg                               |   87 +
 contrib/Doxyfile => doc/doxygen/taler.doxy         |  134 +-
 src/Makefile.am                                    |    2 +-
 src/backend/Makefile.am                            |   70 +-
 src/backend/legacy.c                               |  442 +
 src/backend/merchant.conf                          |    4 +
 src/backend/taler-merchant-httpd.c                 | 1875 ++--
 src/backend/taler-merchant-httpd.h                 |  358 +-
 ...pd_refund.c => taler-merchant-httpd_OBSOLETE.c} |    0
 src/backend/taler-merchant-httpd_auditors.c        |    8 +-
 src/backend/taler-merchant-httpd_auditors.h        |    4 +-
 src/backend/taler-merchant-httpd_check-payment.c   |  591 --
 src/backend/taler-merchant-httpd_config.c          |   95 +-
 src/backend/taler-merchant-httpd_config.h          |   14 +-
 src/backend/taler-merchant-httpd_exchanges.c       |  290 +-
 src/backend/taler-merchant-httpd_exchanges.h       |    6 +-
 src/backend/taler-merchant-httpd_get-orders-ID.c   | 1069 +++
 src/backend/taler-merchant-httpd_get-orders-ID.h   |   57 +
 src/backend/taler-merchant-httpd_get-tips-ID.c     |  123 +
 ...lookup.h => taler-merchant-httpd_get-tips-ID.h} |   28 +-
 src/backend/taler-merchant-httpd_history.c         |  299 -
 src/backend/taler-merchant-httpd_history.h         |   49 -
 src/backend/taler-merchant-httpd_mhd.c             |   40 +-
 src/backend/taler-merchant-httpd_mhd.h             |   26 +-
 src/backend/taler-merchant-httpd_order.c           |  747 --
 src/backend/taler-merchant-httpd_order.h           |   48 -
 src/backend/taler-merchant-httpd_poll-payment.c    |  582 --
 .../taler-merchant-httpd_post-orders-ID-abort.c    | 1014 +++
 ...=> taler-merchant-httpd_post-orders-ID-abort.h} |   35 +-
 .../taler-merchant-httpd_post-orders-ID-claim.c    |  252 +
 ...=> taler-merchant-httpd_post-orders-ID-claim.h} |   30 +-
 ...c => taler-merchant-httpd_post-orders-ID-pay.c} | 2164 ++---
 ...h => taler-merchant-httpd_post-orders-ID-pay.h} |   29 +-
 .../taler-merchant-httpd_post-tips-ID-pickup.c     |  932 ++
 ... => taler-merchant-httpd_post-tips-ID-pickup.h} |   34 +-
 ...er-merchant-httpd_private-delete-instances-ID.c |   90 +
 ...er-merchant-httpd_private-delete-instances-ID.h |   41 +
 ...taler-merchant-httpd_private-delete-orders-ID.c |  100 +
 ...taler-merchant-httpd_private-delete-orders-ID.h |   41 +
 ...ler-merchant-httpd_private-delete-products-ID.c |   85 +
 ...ler-merchant-httpd_private-delete-products-ID.h |   41 +
 ...ler-merchant-httpd_private-delete-reserves-ID.c |  100 +
 ...ler-merchant-httpd_private-delete-reserves-ID.h |   41 +
 ...taler-merchant-httpd_private-get-instances-ID.c |  102 +
 ...taler-merchant-httpd_private-get-instances-ID.h |   41 +
 .../taler-merchant-httpd_private-get-instances.c   |  116 +
 .../taler-merchant-httpd_private-get-instances.h   |   41 +
 .../taler-merchant-httpd_private-get-orders-ID.c   | 1156 +++
 ...> taler-merchant-httpd_private-get-orders-ID.h} |   28 +-
 .../taler-merchant-httpd_private-get-orders.c      |  497 ++
 .../taler-merchant-httpd_private-get-orders.h      |   74 +
 .../taler-merchant-httpd_private-get-products-ID.c |   98 +
 .../taler-merchant-httpd_private-get-products-ID.h |   41 +
 .../taler-merchant-httpd_private-get-products.c    |   85 +
 ...=> taler-merchant-httpd_private-get-products.h} |   29 +-
 .../taler-merchant-httpd_private-get-reserves-ID.c |  217 +
 ...taler-merchant-httpd_private-get-reserves-ID.h} |   22 +-
 .../taler-merchant-httpd_private-get-reserves.c    |  150 +
 ...=> taler-merchant-httpd_private-get-reserves.h} |   29 +-
 .../taler-merchant-httpd_private-get-tips-ID.c     |  159 +
 ... => taler-merchant-httpd_private-get-tips-ID.h} |   26 +-
 .../taler-merchant-httpd_private-get-tips.c        |  161 +
 ...y.h => taler-merchant-httpd_private-get-tips.h} |   28 +-
 .../taler-merchant-httpd_private-get-transfers.c   |  239 +
 ...> taler-merchant-httpd_private-get-transfers.h} |   25 +-
 ...ler-merchant-httpd_private-patch-instances-ID.c |  375 +
 ...ler-merchant-httpd_private-patch-instances-ID.h |   43 +
 ...aler-merchant-httpd_private-patch-products-ID.c |  237 +
 ...aler-merchant-httpd_private-patch-products-ID.h |   43 +
 .../taler-merchant-httpd_private-post-instances.c  |  423 +
 .../taler-merchant-httpd_private-post-instances.h  |   43 +
 ...-merchant-httpd_private-post-orders-ID-refund.c |  273 +
 ...merchant-httpd_private-post-orders-ID-refund.h} |   28 +-
 ...nt-httpd_private-post-orders-ID-track-UNSPEC.c} |    0
 ...nt-httpd_private-post-orders-ID-track-UNSPEC.h} |    0
 .../taler-merchant-httpd_private-post-orders.c     | 1146 +++
 ... => taler-merchant-httpd_private-post-orders.h} |   33 +-
 ...-merchant-httpd_private-post-products-ID-lock.c |  118 +
 ...-merchant-httpd_private-post-products-ID-lock.h |   43 +
 .../taler-merchant-httpd_private-post-products.c   |  242 +
 .../taler-merchant-httpd_private-post-products.h   |   43 +
 ...-httpd_private-post-reserves-ID-authorize-tip.c |  228 +
 ...-httpd_private-post-reserves-ID-authorize-tip.h |   57 +
 .../taler-merchant-httpd_private-post-reserves.c   |  325 +
 .../taler-merchant-httpd_private-post-reserves.h   |   50 +
 .../taler-merchant-httpd_private-post-transfers.c  | 1059 +++
 ... taler-merchant-httpd_private-post-transfers.h} |   38 +-
 src/backend/taler-merchant-httpd_proposal.c        |  228 -
 src/backend/taler-merchant-httpd_refund.h          |   71 -
 src/backend/taler-merchant-httpd_refund_increase.c |  379 -
 src/backend/taler-merchant-httpd_refund_lookup.c   |  654 --
 src/backend/taler-merchant-httpd_reserves.c        |  359 +
 src/backend/taler-merchant-httpd_reserves.h        |   61 +
 src/backend/taler-merchant-httpd_tip-authorize.c   |  306 -
 src/backend/taler-merchant-httpd_tip-pickup.c      |  771 --
 src/backend/taler-merchant-httpd_tip-pickup.h      |   75 -
 src/backend/taler-merchant-httpd_tip-pickup_get.c  |  146 -
 src/backend/taler-merchant-httpd_tip-query.c       |  248 -
 .../taler-merchant-httpd_tip-reserve-helper.c      |  462 -
 .../taler-merchant-httpd_tip-reserve-helper.h      |  153 -
 src/backend/taler-merchant-httpd_track-transfer.c  | 1089 ---
 src/backenddb/Makefile.am                          |    4 +-
 src/backenddb/drop0001.sql                         |   34 +-
 src/backenddb/merchant-0000.sql                    |    2 +-
 src/backenddb/merchant-0001.sql                    |  557 +-
 src/backenddb/merchantdb_plugin.c                  |    4 +-
 src/backenddb/plugin_merchantdb_postgres.c         | 9020 +++++++++++++++-----
 src/backenddb/test_merchantdb.c                    | 6629 ++++++++++++--
 src/include/taler_merchant_service.h               | 3111 +++++--
 src/include/taler_merchant_testing_lib.h           | 1305 ++-
 src/include/taler_merchantdb_lib.h                 |    2 +-
 src/include/taler_merchantdb_plugin.h              | 2213 +++--
 src/lib/Makefile.am                                |  147 +-
 src/lib/merchant_api_check_payment.c               |  314 -
 src/lib/merchant_api_common.c                      |   34 +
 src/lib/merchant_api_delete_instance.c             |  259 +
 src/lib/merchant_api_delete_order.c                |  188 +
 src/lib/merchant_api_delete_product.c              |  200 +
 src/lib/merchant_api_delete_reserve.c              |  262 +
 ...hant_api_config.c => merchant_api_get_config.c} |   92 +-
 src/lib/merchant_api_get_instance.c                |  293 +
 src/lib/merchant_api_get_instances.c               |  292 +
 src/lib/merchant_api_get_orders.c                  |  386 +
 src/lib/merchant_api_get_product.c                 |  278 +
 src/lib/merchant_api_get_products.c                |  265 +
 src/lib/merchant_api_get_reserve.c                 |  317 +
 src/lib/merchant_api_get_reserves.c                |  300 +
 src/lib/merchant_api_get_tips.c                    |  333 +
 src/lib/merchant_api_get_transfers.c               |  351 +
 src/lib/merchant_api_history.c                     |  305 -
 src/lib/merchant_api_lock_product.c                |  265 +
 src/lib/merchant_api_merchant_get_order.c          |  515 ++
 src/lib/merchant_api_merchant_get_tip.c            |  339 +
 src/lib/merchant_api_patch_instance.c              |  315 +
 src/lib/merchant_api_patch_product.c               |  308 +
 src/lib/merchant_api_pay.c                         | 1067 ---
 src/lib/merchant_api_poll_payment.c                |  325 -
 src/lib/merchant_api_post_instances.c              |  307 +
 src/lib/merchant_api_post_order_abort.c            |  482 ++
 ...al_lookup.c => merchant_api_post_order_claim.c} |  194 +-
 src/lib/merchant_api_post_order_pay.c              |  728 ++
 ...increase.c => merchant_api_post_order_refund.c} |  148 +-
 ...t_api_proposal.c => merchant_api_post_orders.c} |  171 +-
 src/lib/merchant_api_post_products.c               |  287 +
 src/lib/merchant_api_post_reserves.c               |  258 +
 src/lib/merchant_api_post_transfers.c              |  326 +
 src/lib/merchant_api_refund.c                      |  371 -
 src/lib/merchant_api_tip_authorize.c               |  142 +-
 src/lib/merchant_api_tip_pickup.c                  |   16 +-
 src/lib/merchant_api_tip_pickup2.c                 |   49 +-
 src/lib/merchant_api_track_transaction.c           |  218 -
 src/lib/merchant_api_track_transfer.c              |  314 -
 src/lib/merchant_api_wallet_get_order.c            |  510 ++
 ...i_tip_query.c => merchant_api_wallet_get_tip.c} |  143 +-
 src/lib/test_merchant_api.c                        | 1010 ---
 src/lib/testing_api_cmd_check_payment.c            |  495 --
 src/lib/testing_api_cmd_history.c                  |  359 -
 src/lib/testing_api_cmd_pay_abort.c                |  595 --
 src/lib/testing_api_cmd_pay_abort_refund.c         |  248 -
 src/lib/testing_api_cmd_poll_payment.c             |  491 --
 src/lib/testing_api_cmd_refund_lookup.c            |  457 -
 src/lib/testing_api_cmd_rewind.c                   |  120 -
 src/lib/testing_api_cmd_tip_query.c                |  297 -
 src/lib/testing_api_cmd_track_transaction.c        |  295 -
 src/lib/testing_api_cmd_track_transfer.c           |  322 -
 src/merchant-tools/Makefile.am                     |   37 +-
 src/merchant-tools/taler-merchant-benchmark.c      |  993 +--
 src/merchant-tools/taler-merchant-setup-reserve.c  |  238 +
 src/{lib => testing}/Makefile.am                   |   94 +-
 src/{lib => testing}/reserve_dtip.priv             |    0
 src/{lib => testing}/reserve_tip.priv              |  Bin
 src/{lib => testing}/test_merchant.priv            |    0
 src/testing/test_merchant_api.c                    | 1192 +++
 src/{lib => testing}/test_merchant_api.conf        |   88 +-
 .../.config/taler/exchange/account-2.json          |    0
 .../.config/taler/merchant/account-3.json          |    0
 .../.config/taler/merchant/default.priv            |    0
 .../.config/taler/merchant/dtip.priv               |    0
 .../.config/taler/merchant/nulltip.priv            |    0
 .../.config/taler/merchant/reserve/dtip.priv       |  Bin
 .../.config/taler/merchant/reserve/nulltip.priv    |    0
 .../.config/taler/merchant/reserve/tip.priv        |    0
 .../.config/taler/merchant/tip.priv                |    0
 .../.config/taler/merchant/tor.priv                |    0
 .../test_merchant_api_home/.config/taler/test.json |    0
 .../share/taler/exchange/offline-keys/master.priv  |    0
 .../.local/share/taler/merchant/merchant.priv      |    0
 .../test_merchant_api_proxy_exchange.conf          |    0
 .../test_merchant_api_proxy_merchant.conf          |    0
 src/{lib => testing}/test_merchant_api_twisted.c   |  164 +-
 .../test_merchant_api_twisted.conf                 |    0
 src/testing/testing_api_cmd_abort_order.c          |  448 +
 .../testing_api_cmd_claim_order.c}                 |  145 +-
 src/{lib => testing}/testing_api_cmd_config.c      |    0
 src/testing/testing_api_cmd_delete_instance.c      |  230 +
 src/testing/testing_api_cmd_delete_order.c         |  179 +
 src/testing/testing_api_cmd_delete_product.c       |  182 +
 src/testing/testing_api_cmd_delete_reserve.c       |  245 +
 src/testing/testing_api_cmd_get_instance.c         |  377 +
 src/testing/testing_api_cmd_get_instances.c        |  266 +
 src/testing/testing_api_cmd_get_orders.c           |  576 ++
 src/testing/testing_api_cmd_get_product.c          |  373 +
 src/testing/testing_api_cmd_get_products.c         |  245 +
 src/testing/testing_api_cmd_get_reserve.c          |  354 +
 src/testing/testing_api_cmd_get_reserves.c         |  264 +
 src/testing/testing_api_cmd_get_tips.c             |  268 +
 src/testing/testing_api_cmd_get_transfers.c        |  357 +
 src/testing/testing_api_cmd_lock_product.c         |  219 +
 src/testing/testing_api_cmd_merchant_get_order.c   |  709 ++
 src/testing/testing_api_cmd_merchant_get_tip.c     |  399 +
 src/testing/testing_api_cmd_patch_instance.c       |  341 +
 src/testing/testing_api_cmd_patch_product.c        |  324 +
 .../testing_api_cmd_pay_order.c}                   |  508 +-
 src/testing/testing_api_cmd_post_instances.c       |  394 +
 .../testing_api_cmd_post_orders.c}                 |  165 +-
 src/testing/testing_api_cmd_post_products.c        |  354 +
 src/testing/testing_api_cmd_post_reserves.c        |  297 +
 src/testing/testing_api_cmd_post_transfers.c       |  518 ++
 .../testing_api_cmd_refund_order.c}                |  117 +-
 .../testing_api_cmd_tip_authorize.c                |  195 +-
 src/{lib => testing}/testing_api_cmd_tip_pickup.c  |   32 +-
 src/testing/testing_api_cmd_wallet_get_order.c     |  330 +
 src/testing/testing_api_cmd_wallet_get_tip.c       |  283 +
 src/{lib => testing}/testing_api_helpers.c         |   21 +-
 .../testing_api_trait_claim_nonce.c}               |   46 +-
 src/{lib => testing}/testing_api_trait_hash.c      |    0
 .../testing_api_trait_merchant_sig.c               |    0
 src/{lib => testing}/testing_api_trait_planchet.c  |    0
 .../testing_api_trait_refund_entry.c               |   14 +-
 src/{lib => testing}/testing_api_trait_string.c    |    0
 src/{lib => testing}/tor_merchant.priv             |    0
 236 files changed, 53642 insertions(+), 24263 deletions(-)
 create mode 100644 doc/doxygen/.gitignore
 create mode 100644 doc/doxygen/Makefile.am
 create mode 100644 doc/doxygen/logo.svg
 rename contrib/Doxyfile => doc/doxygen/taler.doxy (72%)
 create mode 100644 src/backend/legacy.c
 rename src/backend/{taler-merchant-httpd_refund.c => 
taler-merchant-httpd_OBSOLETE.c} (100%)
 delete mode 100644 src/backend/taler-merchant-httpd_check-payment.c
 create mode 100644 src/backend/taler-merchant-httpd_get-orders-ID.c
 create mode 100644 src/backend/taler-merchant-httpd_get-orders-ID.h
 create mode 100644 src/backend/taler-merchant-httpd_get-tips-ID.c
 copy src/backend/{taler-merchant-httpd_refund_lookup.h => 
taler-merchant-httpd_get-tips-ID.h} (52%)
 delete mode 100644 src/backend/taler-merchant-httpd_history.c
 delete mode 100644 src/backend/taler-merchant-httpd_history.h
 delete mode 100644 src/backend/taler-merchant-httpd_order.c
 delete mode 100644 src/backend/taler-merchant-httpd_order.h
 delete mode 100644 src/backend/taler-merchant-httpd_poll-payment.c
 create mode 100644 src/backend/taler-merchant-httpd_post-orders-ID-abort.c
 rename src/backend/{taler-merchant-httpd_tip-authorize.h => 
taler-merchant-httpd_post-orders-ID-abort.h} (53%)
 create mode 100644 src/backend/taler-merchant-httpd_post-orders-ID-claim.c
 rename src/backend/{taler-merchant-httpd_check-payment.h => 
taler-merchant-httpd_post-orders-ID-claim.h} (52%)
 rename src/backend/{taler-merchant-httpd_pay.c => 
taler-merchant-httpd_post-orders-ID-pay.c} (56%)
 rename src/backend/{taler-merchant-httpd_pay.h => 
taler-merchant-httpd_post-orders-ID-pay.h} (57%)
 create mode 100644 src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
 copy src/backend/{taler-merchant-httpd_poll-payment.h => 
taler-merchant-httpd_post-tips-ID-pickup.h} (55%)
 create mode 100644 
src/backend/taler-merchant-httpd_private-delete-instances-ID.c
 create mode 100644 
src/backend/taler-merchant-httpd_private-delete-instances-ID.h
 create mode 100644 src/backend/taler-merchant-httpd_private-delete-orders-ID.c
 create mode 100644 src/backend/taler-merchant-httpd_private-delete-orders-ID.h
 create mode 100644 
src/backend/taler-merchant-httpd_private-delete-products-ID.c
 create mode 100644 
src/backend/taler-merchant-httpd_private-delete-products-ID.h
 create mode 100644 
src/backend/taler-merchant-httpd_private-delete-reserves-ID.c
 create mode 100644 
src/backend/taler-merchant-httpd_private-delete-reserves-ID.h
 create mode 100644 src/backend/taler-merchant-httpd_private-get-instances-ID.c
 create mode 100644 src/backend/taler-merchant-httpd_private-get-instances-ID.h
 create mode 100644 src/backend/taler-merchant-httpd_private-get-instances.c
 create mode 100644 src/backend/taler-merchant-httpd_private-get-instances.h
 create mode 100644 src/backend/taler-merchant-httpd_private-get-orders-ID.c
 copy src/backend/{taler-merchant-httpd_config.h => 
taler-merchant-httpd_private-get-orders-ID.h} (54%)
 create mode 100644 src/backend/taler-merchant-httpd_private-get-orders.c
 create mode 100644 src/backend/taler-merchant-httpd_private-get-orders.h
 create mode 100644 src/backend/taler-merchant-httpd_private-get-products-ID.c
 create mode 100644 src/backend/taler-merchant-httpd_private-get-products-ID.h
 create mode 100644 src/backend/taler-merchant-httpd_private-get-products.c
 copy src/backend/{taler-merchant-httpd_refund_lookup.h => 
taler-merchant-httpd_private-get-products.h} (53%)
 create mode 100644 src/backend/taler-merchant-httpd_private-get-reserves-ID.c
 copy src/backend/{taler-merchant-httpd_tip-query.h => 
taler-merchant-httpd_private-get-reserves-ID.h} (57%)
 create mode 100644 src/backend/taler-merchant-httpd_private-get-reserves.c
 rename src/backend/{taler-merchant-httpd_refund_lookup.h => 
taler-merchant-httpd_private-get-reserves.h} (53%)
 create mode 100644 src/backend/taler-merchant-httpd_private-get-tips-ID.c
 rename src/backend/{taler-merchant-httpd_poll-payment.h => 
taler-merchant-httpd_private-get-tips-ID.h} (55%)
 create mode 100644 src/backend/taler-merchant-httpd_private-get-tips.c
 rename src/backend/{taler-merchant-httpd_tip-query.h => 
taler-merchant-httpd_private-get-tips.h} (55%)
 create mode 100644 src/backend/taler-merchant-httpd_private-get-transfers.c
 rename src/backend/{taler-merchant-httpd_track-transfer.h => 
taler-merchant-httpd_private-get-transfers.h} (53%)
 create mode 100644 
src/backend/taler-merchant-httpd_private-patch-instances-ID.c
 create mode 100644 
src/backend/taler-merchant-httpd_private-patch-instances-ID.h
 create mode 100644 src/backend/taler-merchant-httpd_private-patch-products-ID.c
 create mode 100644 src/backend/taler-merchant-httpd_private-patch-products-ID.h
 create mode 100644 src/backend/taler-merchant-httpd_private-post-instances.c
 create mode 100644 src/backend/taler-merchant-httpd_private-post-instances.h
 create mode 100644 
src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
 rename src/backend/{taler-merchant-httpd_refund_increase.h => 
taler-merchant-httpd_private-post-orders-ID-refund.h} (52%)
 rename src/backend/{taler-merchant-httpd_track-transaction.c => 
taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c} (100%)
 rename src/backend/{taler-merchant-httpd_track-transaction.h => 
taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.h} (100%)
 create mode 100644 src/backend/taler-merchant-httpd_private-post-orders.c
 copy src/backend/{taler-merchant-httpd_config.h => 
taler-merchant-httpd_private-post-orders.h} (51%)
 create mode 100644 
src/backend/taler-merchant-httpd_private-post-products-ID-lock.c
 create mode 100644 
src/backend/taler-merchant-httpd_private-post-products-ID-lock.h
 create mode 100644 src/backend/taler-merchant-httpd_private-post-products.c
 create mode 100644 src/backend/taler-merchant-httpd_private-post-products.h
 create mode 100644 
src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
 create mode 100644 
src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h
 create mode 100644 src/backend/taler-merchant-httpd_private-post-reserves.c
 create mode 100644 src/backend/taler-merchant-httpd_private-post-reserves.h
 create mode 100644 src/backend/taler-merchant-httpd_private-post-transfers.c
 rename src/backend/{taler-merchant-httpd_proposal.h => 
taler-merchant-httpd_private-post-transfers.h} (52%)
 delete mode 100644 src/backend/taler-merchant-httpd_proposal.c
 delete mode 100644 src/backend/taler-merchant-httpd_refund.h
 delete mode 100644 src/backend/taler-merchant-httpd_refund_increase.c
 delete mode 100644 src/backend/taler-merchant-httpd_refund_lookup.c
 create mode 100644 src/backend/taler-merchant-httpd_reserves.c
 create mode 100644 src/backend/taler-merchant-httpd_reserves.h
 delete mode 100644 src/backend/taler-merchant-httpd_tip-authorize.c
 delete mode 100644 src/backend/taler-merchant-httpd_tip-pickup.c
 delete mode 100644 src/backend/taler-merchant-httpd_tip-pickup.h
 delete mode 100644 src/backend/taler-merchant-httpd_tip-pickup_get.c
 delete mode 100644 src/backend/taler-merchant-httpd_tip-query.c
 delete mode 100644 src/backend/taler-merchant-httpd_tip-reserve-helper.c
 delete mode 100644 src/backend/taler-merchant-httpd_tip-reserve-helper.h
 delete mode 100644 src/backend/taler-merchant-httpd_track-transfer.c
 delete mode 100644 src/lib/merchant_api_check_payment.c
 create mode 100644 src/lib/merchant_api_delete_instance.c
 create mode 100644 src/lib/merchant_api_delete_order.c
 create mode 100644 src/lib/merchant_api_delete_product.c
 create mode 100644 src/lib/merchant_api_delete_reserve.c
 rename src/lib/{merchant_api_config.c => merchant_api_get_config.c} (73%)
 create mode 100644 src/lib/merchant_api_get_instance.c
 create mode 100644 src/lib/merchant_api_get_instances.c
 create mode 100644 src/lib/merchant_api_get_orders.c
 create mode 100644 src/lib/merchant_api_get_product.c
 create mode 100644 src/lib/merchant_api_get_products.c
 create mode 100644 src/lib/merchant_api_get_reserve.c
 create mode 100644 src/lib/merchant_api_get_reserves.c
 create mode 100644 src/lib/merchant_api_get_tips.c
 create mode 100644 src/lib/merchant_api_get_transfers.c
 delete mode 100644 src/lib/merchant_api_history.c
 create mode 100644 src/lib/merchant_api_lock_product.c
 create mode 100644 src/lib/merchant_api_merchant_get_order.c
 create mode 100644 src/lib/merchant_api_merchant_get_tip.c
 create mode 100644 src/lib/merchant_api_patch_instance.c
 create mode 100644 src/lib/merchant_api_patch_product.c
 delete mode 100644 src/lib/merchant_api_pay.c
 delete mode 100644 src/lib/merchant_api_poll_payment.c
 create mode 100644 src/lib/merchant_api_post_instances.c
 create mode 100644 src/lib/merchant_api_post_order_abort.c
 rename src/lib/{merchant_api_proposal_lookup.c => 
merchant_api_post_order_claim.c} (51%)
 create mode 100644 src/lib/merchant_api_post_order_pay.c
 rename src/lib/{merchant_api_refund_increase.c => 
merchant_api_post_order_refund.c} (53%)
 rename src/lib/{merchant_api_proposal.c => merchant_api_post_orders.c} (50%)
 create mode 100644 src/lib/merchant_api_post_products.c
 create mode 100644 src/lib/merchant_api_post_reserves.c
 create mode 100644 src/lib/merchant_api_post_transfers.c
 delete mode 100644 src/lib/merchant_api_refund.c
 delete mode 100644 src/lib/merchant_api_track_transaction.c
 delete mode 100644 src/lib/merchant_api_track_transfer.c
 create mode 100644 src/lib/merchant_api_wallet_get_order.c
 rename src/lib/{merchant_api_tip_query.c => merchant_api_wallet_get_tip.c} 
(55%)
 delete mode 100644 src/lib/test_merchant_api.c
 delete mode 100644 src/lib/testing_api_cmd_check_payment.c
 delete mode 100644 src/lib/testing_api_cmd_history.c
 delete mode 100644 src/lib/testing_api_cmd_pay_abort.c
 delete mode 100644 src/lib/testing_api_cmd_pay_abort_refund.c
 delete mode 100644 src/lib/testing_api_cmd_poll_payment.c
 delete mode 100644 src/lib/testing_api_cmd_refund_lookup.c
 delete mode 100644 src/lib/testing_api_cmd_rewind.c
 delete mode 100644 src/lib/testing_api_cmd_tip_query.c
 delete mode 100644 src/lib/testing_api_cmd_track_transaction.c
 delete mode 100644 src/lib/testing_api_cmd_track_transfer.c
 create mode 100644 src/merchant-tools/taler-merchant-setup-reserve.c
 copy src/{lib => testing}/Makefile.am (58%)
 rename src/{lib => testing}/reserve_dtip.priv (100%)
 rename src/{lib => testing}/reserve_tip.priv (100%)
 rename src/{lib => testing}/test_merchant.priv (100%)
 create mode 100644 src/testing/test_merchant_api.c
 rename src/{lib => testing}/test_merchant_api.conf (67%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/exchange/account-2.json (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/account-3.json (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/default.priv (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/dtip.priv (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/nulltip.priv (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv 
(100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/tip.priv (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.config/taler/merchant/tor.priv (100%)
 rename src/{lib => testing}/test_merchant_api_home/.config/taler/test.json 
(100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
 (100%)
 rename src/{lib => 
testing}/test_merchant_api_home/.local/share/taler/merchant/merchant.priv (100%)
 rename src/{lib => testing}/test_merchant_api_proxy_exchange.conf (100%)
 rename src/{lib => testing}/test_merchant_api_proxy_merchant.conf (100%)
 rename src/{lib => testing}/test_merchant_api_twisted.c (86%)
 rename src/{lib => testing}/test_merchant_api_twisted.conf (100%)
 create mode 100644 src/testing/testing_api_cmd_abort_order.c
 rename src/{lib/testing_api_cmd_proposal_lookup.c => 
testing/testing_api_cmd_claim_order.c} (63%)
 rename src/{lib => testing}/testing_api_cmd_config.c (100%)
 create mode 100644 src/testing/testing_api_cmd_delete_instance.c
 create mode 100644 src/testing/testing_api_cmd_delete_order.c
 create mode 100644 src/testing/testing_api_cmd_delete_product.c
 create mode 100644 src/testing/testing_api_cmd_delete_reserve.c
 create mode 100644 src/testing/testing_api_cmd_get_instance.c
 create mode 100644 src/testing/testing_api_cmd_get_instances.c
 create mode 100644 src/testing/testing_api_cmd_get_orders.c
 create mode 100644 src/testing/testing_api_cmd_get_product.c
 create mode 100644 src/testing/testing_api_cmd_get_products.c
 create mode 100644 src/testing/testing_api_cmd_get_reserve.c
 create mode 100644 src/testing/testing_api_cmd_get_reserves.c
 create mode 100644 src/testing/testing_api_cmd_get_tips.c
 create mode 100644 src/testing/testing_api_cmd_get_transfers.c
 create mode 100644 src/testing/testing_api_cmd_lock_product.c
 create mode 100644 src/testing/testing_api_cmd_merchant_get_order.c
 create mode 100644 src/testing/testing_api_cmd_merchant_get_tip.c
 create mode 100644 src/testing/testing_api_cmd_patch_instance.c
 create mode 100644 src/testing/testing_api_cmd_patch_product.c
 rename src/{lib/testing_api_cmd_pay.c => testing/testing_api_cmd_pay_order.c} 
(53%)
 create mode 100644 src/testing/testing_api_cmd_post_instances.c
 rename src/{lib/testing_api_cmd_proposal.c => 
testing/testing_api_cmd_post_orders.c} (64%)
 create mode 100644 src/testing/testing_api_cmd_post_products.c
 create mode 100644 src/testing/testing_api_cmd_post_reserves.c
 create mode 100644 src/testing/testing_api_cmd_post_transfers.c
 rename src/{lib/testing_api_cmd_refund_increase.c => 
testing/testing_api_cmd_refund_order.c} (70%)
 rename src/{lib => testing}/testing_api_cmd_tip_authorize.c (58%)
 rename src/{lib => testing}/testing_api_cmd_tip_pickup.c (92%)
 create mode 100644 src/testing/testing_api_cmd_wallet_get_order.c
 create mode 100644 src/testing/testing_api_cmd_wallet_get_tip.c
 rename src/{lib => testing}/testing_api_helpers.c (90%)
 copy src/{lib/testing_api_trait_refund_entry.c => 
testing/testing_api_trait_claim_nonce.c} (55%)
 rename src/{lib => testing}/testing_api_trait_hash.c (100%)
 rename src/{lib => testing}/testing_api_trait_merchant_sig.c (100%)
 rename src/{lib => testing}/testing_api_trait_planchet.c (100%)
 rename src/{lib => testing}/testing_api_trait_refund_entry.c (86%)
 rename src/{lib => testing}/testing_api_trait_string.c (100%)
 rename src/{lib => testing}/tor_merchant.priv (100%)

diff --git a/.gitignore b/.gitignore
index e1af5ca..de2187a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,14 +30,14 @@ tags
 *.swp
 src/backend/taler-merchant-httpd
 src/merchant-tools/taler-merchant-dbinit
-src/lib/test_merchant_api
-src/lib/test_merchant_api_new
-src/lib/test_merchant_api_home/.local/share/taler/exchange/live-keys/
-src/lib/test_merchant_api_home/.local/share/taler/wirefees/
-src/lib/auditor.in
-src/lib/test_merchant_api_home/.local/share/taler/auditor/
-src/lib/test_merchant_api_home/.local/share/taler/auditors/
-src/lib/test_merchant_api_twisted
+src/testing/test_merchant_api
+src/testing/test_merchant_api_new
+src/testing/test_merchant_api_home/.local/share/taler/exchange/live-keys/
+src/testing/test_merchant_api_home/.local/share/taler/wirefees/
+src/testing/auditor.in
+src/testing/test_merchant_api_home/.local/share/taler/auditor/
+src/testing/test_merchant_api_home/.local/share/taler/auditors/
+src/testing/test_merchant_api_twisted
 taler_merchant_config.h
 taler_merchant_config.h.in
 doc/*
@@ -50,17 +50,19 @@ doc/version-*.texi
 !doc/*.js
 !doc/*.dot
 !doc/examples/
-src/lib/test_merchant_api_home/.local/share/taler/exchange/wirefees/
+!doc/doxygen/
+src/testing/test_merchant_api_home/.local/share/taler/exchange/wirefees/
 src/merchant-tools/taler-merchant-generate-payments
 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
+src/testing/reserve_dkey.priv
+src/testing/reserve_key.priv
 doc/version.texi
 src/merchant-tools/taler-merchant-generate-payments-alt
 src/merchant-tools/taler-merchant-generate-payments
 src/merchant-tools/taler-merchant-benchmark
-/uncrustify.cfg
+uncrustify.cfg
+src/merchant-tools/taler-merchant-setup-reserve
diff --git a/configure.ac b/configure.ac
index 33871db..0f5a5fb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -331,11 +331,13 @@ AM_CONDITIONAL([HAVE_EXPERIMENTAL], [test 
"x$enable_experimental" = "xyes"])
 
 AC_CONFIG_FILES([Makefile
 doc/Makefile
+doc/doxygen/Makefile
 src/Makefile
 src/merchant-tools/Makefile
 src/include/Makefile
 src/backenddb/Makefile
 src/backend/Makefile
 src/lib/Makefile
+src/testing/Makefile
 ])
 AC_OUTPUT
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 1fbae69..114a657 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = . doxygen
+
 man_MANS = \
   taler-merchant-benchmark.1 \
   taler-merchant-httpd.1
diff --git a/doc/doxygen/.gitignore b/doc/doxygen/.gitignore
new file mode 100644
index 0000000..145a3fd
--- /dev/null
+++ b/doc/doxygen/.gitignore
@@ -0,0 +1,2 @@
+html/
+taler-exchange.tag
diff --git a/doc/doxygen/Makefile.am b/doc/doxygen/Makefile.am
new file mode 100644
index 0000000..cde28de
--- /dev/null
+++ b/doc/doxygen/Makefile.am
@@ -0,0 +1,18 @@
+# This Makefile.am is in the public domain
+all:
+       @echo -e \
+"Generate documentation:\n" \
+"\tmake full - full documentation with dependency graphs (slow)\n" \
+"\tmake fast - fast mode without dependency graphs"
+
+full: taler.doxy
+       doxygen $<
+
+fast: taler.doxy
+       sed 's/\(HAVE_DOT.*=\).*/\1 NO/' $< | doxygen -
+
+clean:
+       rm -rf html
+
+EXTRA_DIST = \
+   taler.doxy
diff --git a/doc/doxygen/logo.svg b/doc/doxygen/logo.svg
new file mode 100644
index 0000000..ddb8425
--- /dev/null
+++ b/doc/doxygen/logo.svg
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   viewBox="0 0 180 40"
+   version="1.1"
+   id="svg14"
+   sodipodi:docname="logo-2018-dold.svg"
+   inkscape:version="0.92.2 2405546, 2018-03-11">
+  <metadata
+     id="metadata20">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs18" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1916"
+     inkscape:window-height="1041"
+     id="namedview16"
+     showgrid="false"
+     inkscape:zoom="1.8833333"
+     inkscape:cx="91.061947"
+     inkscape:cy="20"
+     inkscape:window-x="0"
+     inkscape:window-y="18"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="text12" />
+  <style
+     id="style2">
+    .ts1 { fill: #aa3939; letter-spacing:0; }
+    .ts2 { letter-spacing:0; }
+  </style>
+  <g
+     aria-label="❬Taler❭"
+     style="color:#ff0000;font-weight:bold;font-size:36px;font-family:'Lucida 
Console', Monaco, monospace;letter-spacing:0.2em"
+     id="text12">
+    <path
+       d="M 15.978516,31.285156 H 12.234375 L 5.6953125,18.154297 
12.234375,5.0058594 h 3.744141 L 9.4042969,18.154297 Z"
+       style="letter-spacing:0;fill:#aa3939"
+       id="path3725" />
+    <path
+       d="M 35.085937,29 H 29.900391 V 7.2910156 h -6.66211 V 2.7558594 h 
18.509766 v 4.5351562 h -6.66211 z"
+       style=""
+       id="path3727" />
+    <path
+       d="m 62.817188,19.753906 q -2.882812,0 -4.02539,0.738281 
-1.142578,0.738282 -1.142578,2.53125 0,1.335938 0.791015,2.126954 
0.791016,0.791015 2.144531,0.791015 2.039063,0 3.164063,-1.529297 
1.125,-1.546875 1.125,-4.30664 v -0.351563 z m 7.171875,-1.986328 V 29 h 
-5.115234 v -2.197266 q -0.931641,1.300782 -2.390625,2.003907 
-1.458984,0.703125 -3.216797,0.703125 -3.357422,0 -5.238281,-1.775391 
-1.863281,-1.775391 -1.863281,-4.957031 0,-3.445313 2.232421,-5.080078 
2.232422,-1.652344 6 [...]
+       style=""
+       id="path3729" />
+    <path
+       d="M 86.171486,20.791016 V 5.6035156 H 80.950783 V 1.6484375 H 
91.321877 V 20.791016 q 0,2.320312 0.720703,3.287109 0.720703,0.966797 
2.443359,0.966797 H 98.59922 V 29 h -5.554687 q -3.673828,0 -5.273438,-1.898438 
-1.599609,-1.898437 -1.599609,-6.310546 z"
+       style=""
+       id="path3731" />
+    <path
+       d="m 127.59609,28.033203 q -1.79297,0.738281 -3.65625,1.107422 
-1.86328,0.369141 -3.9375,0.369141 -4.93945,0 -7.55859,-2.636719 
-2.60156,-2.654297 -2.60156,-7.628906 0,-4.816407 2.51367,-7.611328 
2.51367,-2.7949224 6.85547,-2.7949224 4.37695,0 6.78515,2.6015624 
2.42578,2.583985 2.42578,7.294922 v 2.091797 h -13.34179 q 0.0176,2.320312 
1.37109,3.46289 1.35352,1.142579 4.04297,1.142579 1.77539,0 3.49805,-0.509766 
1.72265,-0.509766 3.60351,-1.617188 z m -4.35937,-11.074219 q -0.0352, [...]
+       style=""
+       id="path3733" />
+    <path
+       d="m 157.31367,14.744141 q -0.84375,-0.773438 -1.98632,-1.160157 
-1.125,-0.386718 -2.47852,-0.386718 -1.63476,0 -2.86523,0.580078 
-1.21289,0.5625 -1.88086,1.652344 -0.42188,0.667968 -0.59766,1.617187 
-0.1582,0.949219 -0.1582,2.882812 V 29 h -5.15039 V 9.3125 h 5.15039 v 3.058594 
q 0.75586,-1.6875 2.32031,-2.6015627 1.56445,-0.9316407 3.65625,-0.9316407 
1.05469,0 2.05664,0.2636719 1.01953,0.2460938 1.93359,0.7382813 z"
+       style="letter-spacing:0"
+       id="path3735" />
+    <path
+       d="m 164.43282,31.285156 6.55664,-13.130859 -6.53907,-13.1484376 h 
3.72657 l 6.53906,13.1484376 -6.53906,13.130859 z"
+       style="letter-spacing:0;fill:#aa3939"
+       id="path3737" />
+  </g>
+</svg>
diff --git a/contrib/Doxyfile b/doc/doxygen/taler.doxy
similarity index 72%
rename from contrib/Doxyfile
rename to doc/doxygen/taler.doxy
index 3c068db..67e36bd 100644
--- a/contrib/Doxyfile
+++ b/doc/doxygen/taler.doxy
@@ -1,12 +1,13 @@
-# Doxyfile 1.5.5
+# Doxyfile 1.5.6
 
 #---------------------------------------------------------------------------
 # Project related configuration options
 #---------------------------------------------------------------------------
 DOXYFILE_ENCODING      = UTF-8
 PROJECT_NAME           = "GNU Taler: Merchant"
-PROJECT_NUMBER         = 0.3
-OUTPUT_DIRECTORY       = doxygen-doc/
+PROJECT_NUMBER         = 0.6.0
+PROJECT_LOGO           = logo.svg
+OUTPUT_DIRECTORY       = .
 CREATE_SUBDIRS         = YES
 OUTPUT_LANGUAGE        = English
 BRIEF_MEMBER_DESC      = YES
@@ -25,13 +26,15 @@ ABBREVIATE_BRIEF       = "The $name class" \
 ALWAYS_DETAILED_SEC    = NO
 INLINE_INHERITED_MEMB  = NO
 FULL_PATH_NAMES        = YES
-STRIP_FROM_PATH        = .
-STRIP_FROM_INC_PATH    = src/include
+STRIP_FROM_PATH        = ../..
+STRIP_FROM_INC_PATH    = ../../src/include \
+                         src/include \
+                         include
 SHORT_NAMES            = NO
-JAVADOC_AUTOBRIEF      = NO
+JAVADOC_AUTOBRIEF      = YES
 QT_AUTOBRIEF           = NO
 MULTILINE_CPP_IS_BRIEF = NO
-INHERIT_DOCS           = NO
+INHERIT_DOCS           = YES
 SEPARATE_MEMBER_PAGES  = NO
 TAB_SIZE               = 8
 ALIASES                =
@@ -42,6 +45,7 @@ OPTIMIZE_OUTPUT_VHDL   = NO
 BUILTIN_STL_SUPPORT    = NO
 CPP_CLI_SUPPORT        = NO
 SIP_SUPPORT            = NO
+IDL_PROPERTY_SUPPORT   = YES
 DISTRIBUTE_GROUP_DOC   = NO
 SUBGROUPING            = YES
 TYPEDEF_HIDES_STRUCT   = NO
@@ -52,28 +56,30 @@ EXTRACT_ALL            = YES
 EXTRACT_PRIVATE        = NO
 EXTRACT_STATIC         = YES
 EXTRACT_LOCAL_CLASSES  = NO
-EXTRACT_LOCAL_METHODS  = YES
-EXTRACT_ANON_NSPACES   = NO
+EXTRACT_LOCAL_METHODS  = NO
+EXTRACT_ANON_NSPACES   = YES
 HIDE_UNDOC_MEMBERS     = NO
 HIDE_UNDOC_CLASSES     = NO
 HIDE_FRIEND_COMPOUNDS  = NO
 HIDE_IN_BODY_DOCS      = NO
-INTERNAL_DOCS          = NO
+INTERNAL_DOCS          = YES
 CASE_SENSE_NAMES       = YES
 HIDE_SCOPE_NAMES       = NO
 SHOW_INCLUDE_FILES     = YES
 INLINE_INFO            = YES
-SORT_MEMBER_DOCS       = YES
+SORT_MEMBER_DOCS       = NO
 SORT_BRIEF_DOCS        = NO
-SORT_GROUP_NAMES       = NO
+SORT_GROUP_NAMES       = YES
 SORT_BY_SCOPE_NAME     = NO
-GENERATE_TODOLIST      = NO
-GENERATE_TESTLIST      = NO
-GENERATE_BUGLIST       = NO
-GENERATE_DEPRECATEDLIST= NO
+GENERATE_TODOLIST      = YES
+GENERATE_TESTLIST      = YES
+GENERATE_BUGLIST       = YES
+GENERATE_DEPRECATEDLIST= YES
 ENABLED_SECTIONS       =
 MAX_INITIALIZER_LINES  = 30
 SHOW_USED_FILES        = YES
+SHOW_FILES             = YES
+SHOW_NAMESPACES        = YES
 FILE_VERSION_FILTER    =
 #---------------------------------------------------------------------------
 # configuration options related to warning and progress messages
@@ -88,15 +94,62 @@ WARN_LOGFILE           =
 #---------------------------------------------------------------------------
 # configuration options related to the input files
 #---------------------------------------------------------------------------
-INPUT                  = src/
+INPUT                  = ../../src ../../contrib ../../doc
 INPUT_ENCODING         = UTF-8
 FILE_PATTERNS          = *.c \
-                         *.h
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.d \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.idl \
+                         *.odl \
+                         *.cs \
+                         *.php \
+                         *.php3 \
+                         *.inc \
+                         *.m \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.f90 \
+                         *.f \
+                         *.vhd \
+                         *.vhdl \
+                         *.C \
+                         *.CC \
+                         *.C++ \
+                         *.II \
+                         *.I++ \
+                         *.H \
+                         *.HH \
+                         *.H++ \
+                         *.CS \
+                         *.PHP \
+                         *.PHP3 \
+                         *.M \
+                         *.MM \
+                         *.PY \
+                         *.F90 \
+                         *.F \
+                         *.VHD \
+                         *.VHDL
 RECURSIVE              = YES
 EXCLUDE                =
 EXCLUDE_SYMLINKS       = NO
-EXCLUDE_PATTERNS       = */test_* */.git/* */perf_* */tls_test_*  
taler_config.h
-EXCLUDE_SYMBOLS        = GNUNET_* JSON_*
+EXCLUDE_PATTERNS       = */test_* */.svn/* */.git/* */perf_* .*
+EXCLUDE_SYMBOLS        =
 EXAMPLE_PATH           =
 EXAMPLE_PATTERNS       = *
 EXAMPLE_RECURSIVE      = NO
@@ -108,23 +161,23 @@ FILTER_SOURCE_FILES    = NO
 # configuration options related to source browsing
 #---------------------------------------------------------------------------
 SOURCE_BROWSER         = YES
-INLINE_SOURCES         = NO
+INLINE_SOURCES         = YES
 STRIP_CODE_COMMENTS    = YES
 REFERENCED_BY_RELATION = YES
 REFERENCES_RELATION    = YES
 REFERENCES_LINK_SOURCE = YES
 USE_HTAGS              = NO
-VERBATIM_HEADERS       = NO
+VERBATIM_HEADERS       = YES
 #---------------------------------------------------------------------------
 # configuration options related to the alphabetical class index
 #---------------------------------------------------------------------------
 ALPHABETICAL_INDEX     = YES
-COLS_IN_ALPHA_INDEX    = 5
+COLS_IN_ALPHA_INDEX    = 3
 IGNORE_PREFIX          = TALER_
 #---------------------------------------------------------------------------
 # configuration options related to the HTML output
 #---------------------------------------------------------------------------
-#GENERATE_HTML          = YES
+GENERATE_HTML          = YES
 HTML_OUTPUT            = html
 HTML_FILE_EXTENSION    = .html
 HTML_HEADER            =
@@ -132,27 +185,29 @@ HTML_FOOTER            =
 HTML_STYLESHEET        =
 GENERATE_HTMLHELP      = NO
 GENERATE_DOCSET        = NO
-DOCSET_FEEDNAME        = "Doxygen generated docs"
-DOCSET_BUNDLE_ID       = org.doxygen.Project
+DOCSET_FEEDNAME        = "GNU Taler Source Documentation"
+DOCSET_BUNDLE_ID       = net.taler
 HTML_DYNAMIC_SECTIONS  = NO
 CHM_FILE               =
 HHC_LOCATION           =
 GENERATE_CHI           = NO
+CHM_INDEX_ENCODING     =
 BINARY_TOC             = NO
 TOC_EXPAND             = NO
 DISABLE_INDEX          = NO
 ENUM_VALUES_PER_LINE   = 4
-GENERATE_TREEVIEW      = YES
+GENERATE_TREEVIEW      = NONE
 TREEVIEW_WIDTH         = 250
+FORMULA_FONTSIZE       = 10
 #---------------------------------------------------------------------------
 # configuration options related to the LaTeX output
 #---------------------------------------------------------------------------
-#GENERATE_LATEX         = YES
+GENERATE_LATEX         = NO
 LATEX_OUTPUT           = latex
 LATEX_CMD_NAME         = latex
 MAKEINDEX_CMD_NAME     = makeindex
 COMPACT_LATEX          = YES
-PAPER_TYPE             = a4wide
+PAPER_TYPE             = a4
 EXTRA_PACKAGES         =
 LATEX_HEADER           =
 PDF_HYPERLINKS         = YES
@@ -162,9 +217,9 @@ LATEX_HIDE_INDICES     = NO
 #---------------------------------------------------------------------------
 # configuration options related to the RTF output
 #---------------------------------------------------------------------------
-#GENERATE_RTF           = NO
+GENERATE_RTF           = NO
 RTF_OUTPUT             = rtf
-COMPACT_RTF            = YES
+COMPACT_RTF            = NO
 RTF_HYPERLINKS         = NO
 RTF_STYLESHEET_FILE    =
 RTF_EXTENSIONS_FILE    =
@@ -178,7 +233,7 @@ MAN_LINKS              = NO
 #---------------------------------------------------------------------------
 # configuration options related to the XML output
 #---------------------------------------------------------------------------
-#GENERATE_XML           = NO
+GENERATE_XML           = NO
 XML_OUTPUT             = xml
 XML_PROGRAMLISTING     = YES
 #---------------------------------------------------------------------------
@@ -196,28 +251,26 @@ PERLMOD_MAKEVAR_PREFIX =
 # Configuration options related to the preprocessor
 #---------------------------------------------------------------------------
 ENABLE_PREPROCESSING   = YES
-MACRO_EXPANSION        = NO
+MACRO_EXPANSION        = YES
 EXPAND_ONLY_PREDEF     = NO
 SEARCH_INCLUDES        = YES
 INCLUDE_PATH           =
 INCLUDE_FILE_PATTERNS  =
-PREDEFINED             =
+PREDEFINED             = GNUNET_UNUSED="" GNUNET_PACKED=""
 EXPAND_AS_DEFINED      =
 SKIP_FUNCTION_MACROS   = YES
 #---------------------------------------------------------------------------
 # Configuration::additions related to external references
 #---------------------------------------------------------------------------
-TAGFILES               = contrib/gnunet.tag \
-                         contrib/microhttpd.tag \
-                         contrib/taler-exchange.tag
-GENERATE_TAGFILE       = contrib/taler-merchant.tag
+TAGFILES               = ../../contrib/gnunet.tag ../../contrib/microhttpd.tag
+GENERATE_TAGFILE       = taler-exchange.tag
 ALLEXTERNALS           = NO
 EXTERNAL_GROUPS        = YES
 PERL_PATH              = /usr/bin/perl
 #---------------------------------------------------------------------------
 # Configuration options related to the dot tool
 #---------------------------------------------------------------------------
-CLASS_DIAGRAMS         = NO
+CLASS_DIAGRAMS         = YES
 MSCGEN_PATH            =
 HIDE_UNDOC_RELATIONS   = YES
 HAVE_DOT               = YES
@@ -232,7 +285,8 @@ CALL_GRAPH             = YES
 CALLER_GRAPH           = YES
 GRAPHICAL_HIERARCHY    = NO
 DIRECTORY_GRAPH        = YES
-DOT_IMAGE_FORMAT       = png
+DOT_IMAGE_FORMAT       = svg
+INTERACTIVE_SVG        = NO
 DOT_PATH               =
 DOTFILE_DIRS           =
 DOT_GRAPH_MAX_NODES    = 100
diff --git a/src/Makefile.am b/src/Makefile.am
index e58cf01..984c780 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,3 @@
 # This Makefile is in the public domain
 AM_CPPFLAGS = -I$(top_srcdir)/src/include
-SUBDIRS = include backenddb backend lib merchant-tools
+SUBDIRS = include backenddb backend lib testing merchant-tools
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 082c35f..bb3f2bf 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -21,10 +21,76 @@ taler_merchant_httpd_SOURCES = \
   taler-merchant-httpd.c taler-merchant-httpd.h \
   taler-merchant-httpd_auditors.c taler-merchant-httpd_auditors.h \
   taler-merchant-httpd_config.c taler-merchant-httpd_config.h \
-  taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \
   taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \
-  taler-merchant-httpd_history.c taler-merchant-httpd_history.h \
+  taler-merchant-httpd_get-orders-ID.c \
+    taler-merchant-httpd_get-orders-ID.h \
+  taler-merchant-httpd_get-tips-ID.c \
+    taler-merchant-httpd_get-tips-ID.h \
+  taler-merchant-httpd_private-get-tips.c \
+    taler-merchant-httpd_private-get-tips.h \
+  taler-merchant-httpd_private-get-tips-ID.c \
+    taler-merchant-httpd_private-get-tips-ID.h \
   taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \
+  taler-merchant-httpd_private-delete-instances-ID.c \
+    taler-merchant-httpd_private-delete-instances-ID.h \
+  taler-merchant-httpd_private-delete-products-ID.c \
+    taler-merchant-httpd_private-delete-products-ID.h \
+  taler-merchant-httpd_private-delete-orders-ID.c \
+    taler-merchant-httpd_private-delete-orders-ID.h \
+  taler-merchant-httpd_private-delete-reserves-ID.c \
+    taler-merchant-httpd_private-delete-reserves-ID.h \
+  taler-merchant-httpd_private-get-instances.c \
+    taler-merchant-httpd_private-get-instances.h \
+  taler-merchant-httpd_private-get-instances-ID.c \
+    taler-merchant-httpd_private-get-instances-ID.h \
+  taler-merchant-httpd_private-get-products.c \
+    taler-merchant-httpd_private-get-products.h \
+  taler-merchant-httpd_private-get-products-ID.c \
+    taler-merchant-httpd_private-get-products-ID.h \
+  taler-merchant-httpd_private-get-orders.c \
+    taler-merchant-httpd_private-get-orders.h \
+  taler-merchant-httpd_private-get-orders-ID.c \
+    taler-merchant-httpd_private-get-orders-ID.h \
+  taler-merchant-httpd_private-get-reserves.c \
+    taler-merchant-httpd_private-get-reserves.h \
+  taler-merchant-httpd_private-get-reserves-ID.c \
+    taler-merchant-httpd_private-get-reserves-ID.h \
+  taler-merchant-httpd_private-get-transfers.c \
+    taler-merchant-httpd_private-get-transfers.h \
+  taler-merchant-httpd_private-patch-instances-ID.c \
+    taler-merchant-httpd_private-patch-instances-ID.h \
+  taler-merchant-httpd_private-patch-products-ID.c \
+    taler-merchant-httpd_private-patch-products-ID.h \
+  taler-merchant-httpd_private-post-instances.c \
+    taler-merchant-httpd_private-post-instances.h \
+  taler-merchant-httpd_private-post-products.c \
+    taler-merchant-httpd_private-post-products.h \
+  taler-merchant-httpd_private-post-products-ID-lock.c \
+    taler-merchant-httpd_private-post-products-ID-lock.h \
+  taler-merchant-httpd_private-post-reserves.c \
+    taler-merchant-httpd_private-post-reserves.h \
+  taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c \
+    taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h \
+  taler-merchant-httpd_private-post-orders-ID-refund.c \
+    taler-merchant-httpd_private-post-orders-ID-refund.h \
+  taler-merchant-httpd_private-post-orders.c \
+    taler-merchant-httpd_private-post-orders.h \
+  taler-merchant-httpd_private-post-transfers.c \
+    taler-merchant-httpd_private-post-transfers.h \
+  taler-merchant-httpd_post-orders-ID-abort.c \
+    taler-merchant-httpd_post-orders-ID-abort.h \
+  taler-merchant-httpd_post-orders-ID-claim.c \
+    taler-merchant-httpd_post-orders-ID-claim.h \
+  taler-merchant-httpd_post-orders-ID-pay.c \
+    taler-merchant-httpd_post-orders-ID-pay.h \
+  taler-merchant-httpd_post-tips-ID-pickup.c \
+    taler-merchant-httpd_post-tips-ID-pickup.h \
+  taler-merchant-httpd_reserves.c \
+    taler-merchant-httpd_reserves.h
+
+DEAD = \
+  taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \
+  taler-merchant-httpd_history.c taler-merchant-httpd_history.h \
   taler-merchant-httpd_order.c taler-merchant-httpd_order.h \
   taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h \
   taler-merchant-httpd_poll-payment.c taler-merchant-httpd_poll-payment.h \
diff --git a/src/backend/legacy.c b/src/backend/legacy.c
new file mode 100644
index 0000000..2fb7baa
--- /dev/null
+++ b/src/backend/legacy.c
@@ -0,0 +1,442 @@
+/**
+ * Closure for the #wireformat_iterator_cb().
+ */
+struct WireFormatIteratorContext
+{
+  /**
+   * The global iteration context.
+   */
+  struct IterateInstancesCls *iic;
+
+  /**
+   * The merchant instance we are currently building.
+   */
+  struct MerchantInstance *mi;
+
+  /**
+   * Set to #GNUNET_YES if the default instance was found.
+   */
+  int default_instance;
+};
+
+
+/**
+ * Callback that looks for 'merchant-account-*' sections,
+ * and populates our wire method according to the data
+ *
+ * @param cls closure with a `struct WireFormatIteratorContext *`
+ * @section section name this callback gets
+ */
+static void
+wireformat_iterator_cb (void *cls,
+                        const char *section)
+{
+  struct WireFormatIteratorContext *wfic = cls;
+  struct MerchantInstance *mi = wfic->mi;
+  struct IterateInstancesCls *iic = wfic->iic;
+  char *instance_option;
+  struct WireMethod *wm;
+  char *payto;
+  char *fn;
+  json_t *j;
+  struct GNUNET_HashCode jh_wire;
+  char *wire_file_mode;
+
+  if (0 != strncasecmp (section,
+                        "merchant-account-",
+                        strlen ("merchant-account-")))
+    return;
+  GNUNET_asprintf (&instance_option,
+                   "HONOR_%s",
+                   mi->id);
+  if (GNUNET_YES !=
+      GNUNET_CONFIGURATION_get_value_yesno (cfg,
+                                            section,
+                                            instance_option))
+  {
+    GNUNET_free (instance_option);
+    return;
+  }
+  GNUNET_free (instance_option);
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             section,
+                                             "PAYTO_URI",
+                                             &payto))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "PAYTO_URI");
+    iic->ret = GNUNET_SYSERR;
+    return;
+  }
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               section,
+                                               "WIRE_RESPONSE",
+                                               &fn))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "WIRE_RESPONSE");
+    GNUNET_free (payto);
+    iic->ret = GNUNET_SYSERR;
+    return;
+  }
+
+  /* Try loading existing JSON from file */
+  if (GNUNET_YES ==
+      GNUNET_DISK_file_test (fn))
+  {
+    json_error_t err;
+    char *url;
+
+    if (NULL ==
+        (j = json_load_file (fn,
+                             JSON_REJECT_DUPLICATES,
+                             &err)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to load JSON from `%s': %s at %d:%d\n",
+                  fn,
+                  err.text,
+                  err.line,
+                  err.column);
+      GNUNET_free (fn);
+      GNUNET_free (payto);
+      iic->ret = GNUNET_SYSERR;
+      return;
+    }
+    url = TALER_JSON_wire_to_payto (j);
+    if (NULL == url)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "URL missing in `%s', disabling account `%s'\n",
+                  fn,
+                  section);
+      GNUNET_free (fn);
+      GNUNET_free (payto);
+      iic->ret = GNUNET_SYSERR;
+      return;
+    }
+    if (0 != strcasecmp (url,
+                         payto))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "URL `%s' does not match configuration `%s', disabling 
account `%s'\n",
+                  url,
+                  payto,
+                  section);
+      GNUNET_free (fn);
+      GNUNET_free (payto);
+      GNUNET_free (url);
+      iic->ret = GNUNET_SYSERR;
+      return;
+    }
+    GNUNET_free (url);
+  }
+  else /* need to generate JSON */
+  {
+    struct GNUNET_HashCode salt;
+    char *salt_str;
+
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                                &salt,
+                                sizeof (salt));
+    salt_str = GNUNET_STRINGS_data_to_string_alloc (&salt,
+                                                    sizeof (salt));
+    j = json_pack ("{s:s, s:s}",
+                   "payto_uri", payto,
+                   "salt", salt_str);
+    GNUNET_free (salt_str);
+
+    /* Make sure every path component exists.  */
+    if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (fn))
+    {
+      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                                "mkdir",
+                                fn);
+      GNUNET_free (fn);
+      GNUNET_free (payto);
+      json_decref (j);
+      iic->ret = GNUNET_SYSERR;
+      return;
+    }
+
+    if (0 != json_dump_file (j,
+                             fn,
+                             JSON_COMPACT | JSON_SORT_KEYS))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to write hashed wire details to `%s'\n",
+                  fn);
+      GNUNET_free (fn);
+      GNUNET_free (payto);
+      json_decref (j);
+      iic->ret = GNUNET_SYSERR;
+      return;
+    }
+
+    if (GNUNET_OK ==
+        GNUNET_CONFIGURATION_get_value_string (cfg,
+                                               section,
+                                               "WIRE_FILE_MODE",
+                                               &wire_file_mode))
+    {
+      errno = 0;
+      mode_t mode = (mode_t) strtoul (wire_file_mode,
+                                      NULL,
+                                      8);
+      if (0 != errno)
+      {
+        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                   section,
+                                   "WIRE_FILE_MODE",
+                                   "Must be octal number\n");
+        iic->ret = GNUNET_SYSERR;
+        GNUNET_free (fn);
+        return;
+      }
+      if (0 != chmod (fn, mode))
+      {
+        TALER_LOG_ERROR ("chmod failed on %s\n", fn);
+        iic->ret = GNUNET_SYSERR;
+        GNUNET_free (fn);
+        return;
+      }
+    }
+  }
+
+  GNUNET_free (fn);
+
+  if (GNUNET_OK !=
+      TALER_JSON_merchant_wire_signature_hash (j,
+                                               &jh_wire))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to hash wire input\n");
+    GNUNET_free (fn);
+    GNUNET_free (payto);
+    json_decref (j);
+    iic->ret = GNUNET_SYSERR;
+    return;
+  }
+
+  wm = GNUNET_new (struct WireMethod);
+  wm->wire_method = TALER_payto_get_method (payto);
+  GNUNET_free (payto);
+  GNUNET_asprintf (&instance_option,
+                   "ACTIVE_%s",
+                   mi->id);
+  wm->active = GNUNET_CONFIGURATION_get_value_yesno (cfg,
+                                                     section,
+                                                     instance_option);
+  GNUNET_free (instance_option);
+  if (GNUNET_YES == wm->active)
+    GNUNET_CONTAINER_DLL_insert (mi->wm_head,
+                                 mi->wm_tail,
+                                 wm);
+  else
+    GNUNET_CONTAINER_DLL_insert_tail (mi->wm_head,
+                                      mi->wm_tail,
+                                      wm);
+  wm->j_wire = j;
+  wm->h_wire = jh_wire;
+}
+
+
+/**
+ * Callback that looks for 'instance-*' sections,
+ * and populates accordingly each instance's data
+ *
+ * @param cls closure of type `struct IterateInstancesCls`
+ * @section section name this callback gets
+ */
+static void
+instances_iterator_cb (void *cls,
+                       const char *section)
+{
+  struct IterateInstancesCls *iic = cls;
+  char *token;
+  struct MerchantInstance *mi;
+  /* used as hashmap keys */
+  struct GNUNET_HashCode h_pk;
+  struct GNUNET_HashCode h_id;
+
+  if (0 != strncasecmp (section,
+                        "instance-",
+                        strlen ("instance-")))
+    return;
+  /** Get id **/
+  token = strrchr (section, '-');
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Extracted token: %s\n",
+              token + 1);
+  mi = GNUNET_new (struct MerchantInstance);
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             section,
+                                             "NAME",
+                                             &mi->name))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "NAME");
+    GNUNET_free (mi);
+    iic->ret = GNUNET_SYSERR;
+    return;
+  }
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               section,
+                                               "KEYFILE",
+                                               &mi->keyfile))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "KEYFILE");
+    GNUNET_free (mi->name);
+    GNUNET_free (mi);
+    iic->ret = GNUNET_SYSERR;
+    return;
+  }
+  if (GNUNET_OK ==
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             section,
+                                             "TIP_EXCHANGE",
+                                             &mi->tip_exchange))
+  {
+    char *tip_reserves;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                                 section,
+                                                 "TIP_RESERVE_PRIV_FILENAME",
+                                                 &tip_reserves))
+    {
+      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "TIP_RESERVE_PRIV_FILENAME");
+      GNUNET_free (mi->keyfile);
+      GNUNET_free (mi->name);
+      GNUNET_free (mi);
+      iic->ret = GNUNET_SYSERR;
+      return;
+    }
+    if (GNUNET_OK !=
+        GNUNET_CRYPTO_eddsa_key_from_file (tip_reserves,
+                                           GNUNET_NO,
+                                           &mi->tip_reserve.eddsa_priv))
+    {
+      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->keyfile);
+      GNUNET_free (mi->name);
+      GNUNET_free (mi);
+      iic->ret = GNUNET_SYSERR;
+      return;
+    }
+    GNUNET_free (tip_reserves);
+  }
+
+  if (GNUNET_SYSERR ==
+      GNUNET_CRYPTO_eddsa_key_from_file (mi->keyfile,
+                                         GNUNET_YES,
+                                         &mi->privkey.eddsa_priv))
+  {
+    GNUNET_break (0);
+    GNUNET_free (mi->keyfile);
+    GNUNET_free (mi->name);
+    GNUNET_free (mi);
+    iic->ret = GNUNET_SYSERR;
+    return;
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&mi->privkey.eddsa_priv,
+                                      &mi->pubkey.eddsa_pub);
+
+  mi->id = GNUNET_strdup (token + 1);
+  if (0 == strcasecmp ("default",
+                       mi->id))
+    iic->default_instance = GNUNET_YES;
+
+  GNUNET_CRYPTO_hash (mi->id,
+                      strlen (mi->id),
+                      &h_id);
+  if (GNUNET_OK !=
+      GNUNET_CONTAINER_multihashmap_put (by_id_map,
+                                         &h_id,
+                                         mi,
+                                         
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to put an entry into the 'by_id' hashmap\n");
+    iic->ret = GNUNET_SYSERR;
+    GNUNET_free (mi->keyfile);
+    GNUNET_free (mi->name);
+    GNUNET_free (mi);
+    return;
+  }
+  GNUNET_CRYPTO_hash (&mi->pubkey.eddsa_pub,
+                      sizeof (struct GNUNET_CRYPTO_EddsaPublicKey),
+                      &h_pk);
+
+
+  /* Initialize wireformats */
+  {
+    struct WireFormatIteratorContext wfic = {
+      .iic = iic,
+      .mi = mi
+    };
+
+    GNUNET_CONFIGURATION_iterate_sections (cfg,
+                                           &wireformat_iterator_cb,
+                                           &wfic);
+  }
+  if (NULL == mi->wm_head)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to load wire formats for instance `%s'\n",
+                mi->id);
+    iic->ret = GNUNET_SYSERR;
+  }
+
+}
+
+
+/**
+ * Iterate over each merchant instance, in order to populate
+ * each instance's own data
+ *
+ * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors
+ *          (for example, if no "default" instance is defined)
+ */
+static int
+iterate_instances (void)
+{
+  struct IterateInstancesCls iic;
+
+  iic.default_instance = GNUNET_NO;
+  iic.ret = GNUNET_OK;
+  GNUNET_CONFIGURATION_iterate_sections (cfg,
+                                         &instances_iterator_cb,
+                                         &iic);
+
+  if (GNUNET_NO == iic.default_instance)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "No default merchant instance found\n");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK != iic.ret)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "At least one instance was not successfully parsed\n");
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf
index 4aac2da..6a0cc53 100644
--- a/src/backend/merchant.conf
+++ b/src/backend/merchant.conf
@@ -17,6 +17,10 @@ PORT = 9966
 # if left empty.  Only used if "SERVE" is 'tcp'.
 # BIND_TO =
 
+# How long do we keep contract / payment information around after the
+# purchase (for tax records and other legal reasons).
+LEGAL_PRESERVATION = 11 years
+
 
 # Which unix domain path should we bind to? Only used if "SERVE" is 'unix'.
 UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http
diff --git a/src/backend/taler-merchant-httpd.c 
b/src/backend/taler-merchant-httpd.c
index 86b3630..cbc7774 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2018 Taler Systems SA
+  (C) 2014-2020 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
@@ -22,137 +22,94 @@
  * @author Florian Dold
  */
 #include "platform.h"
-#include <microhttpd.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_mhd_lib.h>
 #include <taler/taler_bank_service.h>
 #include <taler/taler_exchange_service.h>
-#include "taler_merchantdb_lib.h"
-#include "taler-merchant-httpd.h"
 #include "taler-merchant-httpd_auditors.h"
-#include "taler-merchant-httpd_check-payment.h"
+#include "taler-merchant-httpd_config.h"
 #include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_history.h"
+#include "taler-merchant-httpd_get-orders-ID.h"
+#include "taler-merchant-httpd_get-tips-ID.h"
 #include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_order.h"
-#include "taler-merchant-httpd_pay.h"
-#include "taler-merchant-httpd_poll-payment.h"
-#include "taler-merchant-httpd_proposal.h"
-#include "taler-merchant-httpd_refund.h"
-#include "taler-merchant-httpd_refund_increase.h"
-#include "taler-merchant-httpd_refund_lookup.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-pickup.h"
-#include "taler-merchant-httpd_tip-query.h"
-#include "taler-merchant-httpd_tip-reserve-helper.h"
-#include "taler-merchant-httpd_config.h"
+#include "taler-merchant-httpd_private-delete-instances-ID.h"
+#include "taler-merchant-httpd_private-delete-products-ID.h"
+#include "taler-merchant-httpd_private-delete-orders-ID.h"
+#include "taler-merchant-httpd_private-delete-reserves-ID.h"
+#include "taler-merchant-httpd_private-get-instances.h"
+#include "taler-merchant-httpd_private-get-instances-ID.h"
+#include "taler-merchant-httpd_private-get-products.h"
+#include "taler-merchant-httpd_private-get-products-ID.h"
+#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler-merchant-httpd_private-get-orders-ID.h"
+#include "taler-merchant-httpd_private-get-reserves.h"
+#include "taler-merchant-httpd_private-get-reserves-ID.h"
+#include "taler-merchant-httpd_private-get-tips-ID.h"
+#include "taler-merchant-httpd_private-get-tips.h"
+#include "taler-merchant-httpd_private-get-transfers.h"
+#include "taler-merchant-httpd_private-patch-instances-ID.h"
+#include "taler-merchant-httpd_private-patch-products-ID.h"
+#include "taler-merchant-httpd_private-post-instances.h"
+#include "taler-merchant-httpd_private-post-orders.h"
+#include "taler-merchant-httpd_private-post-orders-ID-refund.h"
+#include "taler-merchant-httpd_private-post-products.h"
+#include "taler-merchant-httpd_private-post-products-ID-lock.h"
+#include "taler-merchant-httpd_private-post-reserves.h"
+#include "taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h"
+#include "taler-merchant-httpd_private-post-transfers.h"
+#include "taler-merchant-httpd_post-orders-ID-abort.h"
+#include "taler-merchant-httpd_post-orders-ID-claim.h"
+#include "taler-merchant-httpd_post-orders-ID-pay.h"
+#include "taler-merchant-httpd_post-tips-ID-pickup.h"
+#include "taler-merchant-httpd_reserves.h"
 
 /**
  * Backlog for listen operation on unix-domain sockets.
  */
 #define UNIX_BACKLOG 500
 
-
 /**
- * Used by the iterator of the various merchant's instances given
- * in configuration
+ * Default maximum upload size permitted.  Can be overridden
+ * per handler.
  */
-struct IterateInstancesCls
-{
-
-  /**
-   * Current index in the global array of #MerchantInstance
-   * types. Used by the callback in order to know which index
-   * is associated to the element being processed.
-   */
-  unsigned int current_index;
-
-  /**
-   * Flag indicating whether config contains a default instance
-   */
-  unsigned int default_instance;
-
-  /**
-   * Tells if the parsing encountered any error. We need this
-   * field since the iterator must return void
-   */
-  unsigned int ret;
-};
-
+#define DEFAULT_MAX_UPLOAD_SIZE (16 * 1024)
 
 /**
- * Hashmap pointing at merchant instances by 'id'. An 'id' is
- * just a string that identifies a merchant instance. When a frontend
- * needs to specify an instance to the backend, it does so by 'id'
- */
-struct GNUNET_CONTAINER_MultiHashMap *by_id_map;
-
-/**
- * Hashmap pointing at merchant instances by public key. This map
- * is mainly used to check whether there is more than one instance
- * using the same key
- */
-struct GNUNET_CONTAINER_MultiHashMap *by_kpub_map;
-
-/**
- * The port we are running on
- */
-static uint16_t port;
-
-/**
- * This value tells the exchange by which date this merchant would like
- * to receive the funds for a deposited payment
+ * Which currency do we use?
  */
-struct GNUNET_TIME_Relative default_wire_transfer_delay;
+char *TMH_currency;
 
 /**
- * Locations from the configuration.  Mapping from
- * label to location data.
+ * Inform the auditor for all deposit confirmations (global option)
  */
-json_t *default_locations;
+int TMH_force_audit;
 
 /**
- * If the frontend does NOT specify a payment deadline, how long should
- * offers we make be valid by default?
+ * Connection handle to the our database
  */
-struct GNUNET_TIME_Relative default_pay_deadline;
+struct TALER_MERCHANTDB_Plugin *TMH_db;
 
 /**
- * Default maximum wire fee to assume, unless stated differently in the 
proposal
- * already.
+ * Hashmap pointing at merchant instances by 'id'. An 'id' is
+ * just a string that identifies a merchant instance. When a frontend
+ * needs to specify an instance to the backend, it does so by 'id'
  */
-struct TALER_Amount default_max_wire_fee;
+struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;
 
 /**
- * Default max deposit fee that the merchant is willing to
- * pay; if deposit costs more, then the customer will cover
- * the difference.
+ * How long do we need to keep information on paid contracts on file for tax
+ * or other legal reasons?  Used to block deletions for younger transaction
+ * data.
  */
-struct TALER_Amount default_max_deposit_fee;
+struct GNUNET_TIME_Relative TMH_legal_expiration;
 
 /**
- * Default factor for wire fee amortization.
+ * The port we are running on
  */
-unsigned long long default_wire_fee_amortization;
+static uint16_t port;
 
 /**
  * Should a "Connection: close" header be added to each HTTP response?
  */
-static int TMH_merchant_connection_close;
-
-/**
- * Which currency do we use?
- */
-char *TMH_currency;
-
-/**
- * Inform the auditor for all deposit confirmations (global option)
- */
-int TMH_force_audit;
+static int merchant_connection_close;
 
 /**
  * Task running the HTTP server.
@@ -164,11 +121,6 @@ static struct GNUNET_SCHEDULER_Task *mhd_task;
  */
 static int result;
 
-/**
- * Connection handle to the our database
- */
-struct TALER_MERCHANTDB_Plugin *db;
-
 /**
  * The MHD Daemon
  */
@@ -178,42 +130,47 @@ static struct MHD_Daemon *mhd;
  * MIN-Heap of suspended connections to resume when the timeout expires,
  * ordered by timeout. Values are of type `struct MHD_Connection`
  */
-struct GNUNET_CONTAINER_Heap *resume_timeout_heap;
+static struct GNUNET_CONTAINER_Heap *resume_timeout_heap;
 
 /**
  * Hash map from H(order_id,merchant_pub) to `struct MHD_Connection`
  * entries to resume when a payment is made for the given order.
  */
-struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map;
+static struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map;
 
 /**
  * Task responsible for timeouts in the #resume_timeout_heap.
  */
-struct GNUNET_SCHEDULER_Task *resume_timeout_task;
+static struct GNUNET_SCHEDULER_Task *resume_timeout_task;
 
 /**
  * Our configuration.
  */
-static struct GNUNET_CONFIGURATION_Handle *cfg;
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
 
 
 /**
- * Callback that frees all the elements in the hashmap
+ * Decrement reference counter of @a mi, and free if it hits zero.
  *
- * @param cls closure, NULL
- * @param key current key
- * @param value a `struct MerchantInstance`
+ * @param[in,out] mi merchant instance to update and possibly free
  */
-static int
-hashmap_free (void *cls,
-              const struct GNUNET_HashCode *key,
-              void *value)
+void
+TMH_instance_decref (struct TMH_MerchantInstance *mi)
 {
-  struct MerchantInstance *mi = value;
-  struct WireMethod *wm;
+  struct TMH_WireMethod *wm;
+  struct GNUNET_HashCode h_instance;
 
-  (void) cls;
-  (void) key;
+  mi->rc--;
+  if (0 != mi->rc)
+    return;
+  GNUNET_CRYPTO_hash (mi->settings.id,
+                      strlen (mi->settings.id),
+                      &h_instance);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CONTAINER_multihashmap_remove (TMH_by_id_map,
+                                                       &h_instance,
+                                                       mi));
+  TMH_force_get_orders_resume (mi);
   while (NULL != (wm = (mi->wm_head)))
   {
     GNUNET_CONTAINER_DLL_remove (mi->wm_head,
@@ -224,11 +181,31 @@ hashmap_free (void *cls,
     GNUNET_free (wm);
   }
 
-  GNUNET_free (mi->id);
-  GNUNET_free (mi->keyfile);
-  GNUNET_free (mi->name);
-  GNUNET_free_non_null (mi->tip_exchange);
+  GNUNET_free (mi->settings.id);
+  GNUNET_free (mi->settings.name);
+  json_decref (mi->settings.address);
+  json_decref (mi->settings.jurisdiction);
   GNUNET_free (mi);
+}
+
+
+/**
+ * Callback that frees all the instances in the hashmap
+ *
+ * @param cls closure, NULL
+ * @param key current key
+ * @param value a `struct TMH_MerchantInstance`
+ */
+static int
+instance_free_cb (void *cls,
+                  const struct GNUNET_HashCode *key,
+                  void *value)
+{
+  struct TMH_MerchantInstance *mi = value;
+
+  (void) cls;
+  (void) key;
+  TMH_instance_decref (mi);
   return GNUNET_YES;
 }
 
@@ -266,10 +243,10 @@ payment_trigger_free (void *cls,
  * @param mpub an instance public key
  * @param key[out] set to the hash map key to use
  */
-void
-TMH_compute_pay_key (const char *order_id,
-                     const struct TALER_MerchantPublicKeyP *mpub,
-                     struct GNUNET_HashCode *key)
+static void
+compute_pay_key (const char *order_id,
+                 const struct TALER_MerchantPublicKeyP *mpub,
+                 struct GNUNET_HashCode *key)
 {
   size_t olen = strlen (order_id);
   char buf[sizeof (*mpub) + olen];
@@ -287,7 +264,6 @@ TMH_compute_pay_key (const char *order_id,
               "Pay key for %s is %s\n",
               order_id,
               GNUNET_h2s (key));
-
 }
 
 
@@ -322,6 +298,7 @@ do_resume (void *cls)
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Resuming long polled job due to timeout\n");
     MHD_resume_connection (sc->con);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
   }
   resume_timeout_task = GNUNET_SCHEDULER_add_at (sc->long_poll_timeout,
                                                  &do_resume,
@@ -332,14 +309,21 @@ do_resume (void *cls)
 /**
  * Suspend connection from @a sc until payment has been received.
  *
+ * @param order_id the order that we are waiting on
+ * @param mi the merchant instance we are waiting on
  * @param sc connection to suspend
  * @param min_refund refund amount we are waiting on to be exceeded before 
resuming,
  *                   NULL if we are not waiting for refunds
  */
 void
-TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc,
+TMH_long_poll_suspend (const char *order_id,
+                       const struct TMH_MerchantInstance *mi,
+                       struct TMH_SuspendedConnection *sc,
                        const struct TALER_Amount *min_refund)
 {
+  compute_pay_key (order_id,
+                   &mi->merchant_pub,
+                   &sc->key);
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Suspending operation on key %s\n",
               GNUNET_h2s (&sc->key));
@@ -350,7 +334,7 @@ TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc,
                                                     
GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
   if (NULL != min_refund)
   {
-    sc->awaiting_refund = GNUNET_YES;
+    sc->awaiting_refund = true;
     sc->refund_expected = *min_refund;
   }
   sc->hn = GNUNET_CONTAINER_heap_insert (resume_timeout_heap,
@@ -385,7 +369,7 @@ resume_operation (void *cls,
   const struct TALER_Amount *have_refund = cls;
   struct TMH_SuspendedConnection *sc = value;
 
-  if ( (GNUNET_YES == sc->awaiting_refund) &&
+  if ( (sc->awaiting_refund) &&
        ( (NULL == have_refund) ||
          (1 != TALER_amount_cmp (have_refund,
                                  &sc->refund_expected)) ) )
@@ -415,20 +399,20 @@ resume_operation (void *cls,
  * Find out if we have any clients long-polling for @a order_id to be
  * confirmed at merchant @a mpub, and if so, tell them to resume.
  *
- * @param order_id the order that was paid
- * @param mpub the merchant's public key of the instance where the payment 
happened
+ * @param order_id the order that was paid or refunded
+ * @param mi the merchant instance where the payment or refund happened
  * @param have_refund refunded amount, NULL if there was no refund
  */
 void
 TMH_long_poll_resume (const char *order_id,
-                      const struct TALER_MerchantPublicKeyP *mpub,
+                      const struct TMH_MerchantInstance *mi,
                       const struct TALER_Amount *have_refund)
 {
   struct GNUNET_HashCode key;
 
-  TMH_compute_pay_key (order_id,
-                       mpub,
-                       &key);
+  compute_pay_key (order_id,
+                   &mi->merchant_pub,
+                   &key);
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Resuming operations suspended pending payment on key %s\n",
               GNUNET_h2s (&key));
@@ -442,73 +426,6 @@ TMH_long_poll_resume (const char *order_id,
 }
 
 
-/**
- * Create a taler://pay/ URI for the given @a con and @a order_id
- * and @a session_id and @a instance_id.
- *
- * @param con HTTP connection
- * @param order_id the order id
- * @param session_id session, may be NULL
- * @param instance_id instance, may be "default"
- * @return corresponding taler://pay/ URI, or NULL on missing "host"
- */
-char *
-TMH_make_taler_pay_uri (struct MHD_Connection *con,
-                        const char *order_id,
-                        const char *session_id,
-                        const char *instance_id)
-{
-  const char *host;
-  const char *forwarded_host;
-  const char *uri_path;
-  const char *uri_instance_id;
-  const char *query;
-  char *result;
-
-  host = MHD_lookup_connection_value (con,
-                                      MHD_HEADER_KIND,
-                                      "Host");
-  forwarded_host = MHD_lookup_connection_value (con,
-                                                MHD_HEADER_KIND,
-                                                "X-Forwarded-Host");
-
-  uri_path = MHD_lookup_connection_value (con,
-                                          MHD_HEADER_KIND,
-                                          "X-Forwarded-Prefix");
-  if (NULL == uri_path)
-    uri_path = "-";
-  if (NULL != forwarded_host)
-    host = forwarded_host;
-  if (0 == strcmp (instance_id,
-                   "default"))
-    uri_instance_id = "-";
-  else
-    uri_instance_id = instance_id;
-  if (NULL == host)
-  {
-    /* Should never happen, at least the host header should be defined */
-    GNUNET_break (0);
-    return NULL;
-  }
-
-  if (GNUNET_YES == TALER_mhd_is_https (con))
-    query = "";
-  else
-    query = "?insecure=1";
-  GNUNET_assert (NULL != order_id);
-  GNUNET_assert (0 < GNUNET_asprintf (&result,
-                                      "taler://pay/%s/%s/%s/%s%s%s%s",
-                                      host,
-                                      uri_path,
-                                      uri_instance_id,
-                                      order_id,
-                                      (NULL == session_id) ? "" : "/",
-                                      (NULL == session_id) ? "" : session_id,
-                                      query));
-  return result;
-}
-
-
 /**
  * Shutdown task (magically invoked when the application is being
  * quit)
@@ -521,10 +438,11 @@ do_shutdown (void *cls)
   struct TMH_SuspendedConnection *sc;
 
   (void) cls;
-  MH_force_pc_resume ();
-  MH_force_trh_resume ();
-  MH_force_refund_resume ();
-  MH_force_tip_pickup_resume ();
+  TMH_force_ac_resume ();
+  TMH_force_pc_resume ();
+  TMH_force_rc_resume ();
+  TMH_force_post_transfers_resume ();
+  TMH_force_tip_pickup_resume ();
   if (NULL != mhd_task)
   {
     GNUNET_SCHEDULER_cancel (mhd_task);
@@ -556,10 +474,11 @@ do_shutdown (void *cls)
     MHD_stop_daemon (mhd);
     mhd = NULL;
   }
-  if (NULL != db)
+  TMH_RESERVES_done ();
+  if (NULL != TMH_db)
   {
-    TALER_MERCHANTDB_plugin_unload (db);
-    db = NULL;
+    TALER_MERCHANTDB_plugin_unload (TMH_db);
+    TMH_db = NULL;
   }
   TMH_EXCHANGES_done ();
   TMH_AUDITORS_done ();
@@ -571,18 +490,13 @@ do_shutdown (void *cls)
     GNUNET_CONTAINER_multihashmap_destroy (payment_trigger_map);
     payment_trigger_map = NULL;
   }
-  if (NULL != by_id_map)
+  if (NULL != TMH_by_id_map)
   {
-    GNUNET_CONTAINER_multihashmap_iterate (by_id_map,
-                                           &hashmap_free,
+    GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
+                                           &instance_free_cb,
                                            NULL);
-    GNUNET_CONTAINER_multihashmap_destroy (by_id_map);
-    by_id_map = NULL;
-  }
-  if (NULL != by_kpub_map)
-  {
-    GNUNET_CONTAINER_multihashmap_destroy (by_kpub_map);
-    by_kpub_map = NULL;
+    GNUNET_CONTAINER_multihashmap_destroy (TMH_by_id_map);
+    TMH_by_id_map = NULL;
   }
 }
 
@@ -607,15 +521,23 @@ handle_mhd_completion_callback (void *cls,
                                 void **con_cls,
                                 enum MHD_RequestTerminationCode toe)
 {
-  struct TM_HandlerContext *hc = *con_cls;
+  struct TMH_HandlerContext *hc = *con_cls;
 
   if (NULL == hc)
     return;
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Finished handling request for `%s' with status %d\n",
-              hc->rh->url,
+              hc->url,
               (int) toe);
-  hc->cc (hc);
+  if (NULL != hc->cc)
+    hc->cc (hc->ctx);
+  TALER_MHD_parse_post_cleanup_callback (hc->json_parse_context);
+  GNUNET_free (hc->infix);
+  if (NULL != hc->request_body)
+    json_decref (hc->request_body);
+  if (NULL != hc->instance)
+    TMH_instance_decref (hc->instance);
+  GNUNET_free (hc);
   *con_cls = NULL;
 }
 
@@ -625,7 +547,6 @@ handle_mhd_completion_callback (void *cls,
  * starts the task waiting for them.
  */
 static struct GNUNET_SCHEDULER_Task *
-
 prepare_daemon (void);
 
 
@@ -728,505 +649,19 @@ prepare_daemon (void)
 }
 
 
-/**
- * Callback that looks for 'merchant-location-*' sections,
- * and populates @a default_locations.
- *
- * @param cls closure
- * @section section name this callback gets
- */
-static void
-locations_iterator_cb (void *cls,
-                       const char *section)
-{
-  static const char *keys[] = {
-    "country",
-    "city",
-    "state",
-    "region",
-    "province",
-    "zip_code",
-    "street",
-    "street_number",
-    NULL,
-  };
-  const char *prefix = "merchant-location-";
-  const char *substr = strstr (section, prefix);
-  const char *locname;
-  json_t *loc;
-
-  (void) cls;
-  if ( (NULL == substr) || (substr != section) )
-    return;
-  locname = section + strlen (prefix);
-  if (0 == strlen (locname))
-    return;
-  GNUNET_assert (json_is_object (default_locations));
-
-  loc = json_object ();
-  json_object_set_new (default_locations,
-                       locname,
-                       loc);
-  for (unsigned int pos = 0; NULL != keys[pos]; pos++)
-  {
-    char *val;
-
-    if (GNUNET_OK ==
-        GNUNET_CONFIGURATION_get_value_string (cfg,
-                                               section,
-                                               keys[pos],
-                                               &val))
-    {
-      json_object_set_new (loc,
-                           keys[pos],
-                           json_string (val));
-      GNUNET_free (val);
-    }
-  }
-}
-
-
-/**
- * Closure for the #wireformat_iterator_cb().
- */
-struct WireFormatIteratorContext
-{
-  /**
-   * The global iteration context.
-   */
-  struct IterateInstancesCls *iic;
-
-  /**
-   * The merchant instance we are currently building.
-   */
-  struct MerchantInstance *mi;
-
-  /**
-   * Set to #GNUNET_YES if the default instance was found.
-   */
-  int default_instance;
-};
-
-
-/**
- * Callback that looks for 'merchant-account-*' sections,
- * and populates our wire method according to the data
- *
- * @param cls closure with a `struct WireFormatIteratorContext *`
- * @section section name this callback gets
- */
-static void
-wireformat_iterator_cb (void *cls,
-                        const char *section)
-{
-  struct WireFormatIteratorContext *wfic = cls;
-  struct MerchantInstance *mi = wfic->mi;
-  struct IterateInstancesCls *iic = wfic->iic;
-  char *instance_option;
-  struct WireMethod *wm;
-  char *payto;
-  char *fn;
-  json_t *j;
-  struct GNUNET_HashCode jh_wire;
-  char *wire_file_mode;
-
-  if (0 != strncasecmp (section,
-                        "merchant-account-",
-                        strlen ("merchant-account-")))
-    return;
-  GNUNET_asprintf (&instance_option,
-                   "HONOR_%s",
-                   mi->id);
-  if (GNUNET_YES !=
-      GNUNET_CONFIGURATION_get_value_yesno (cfg,
-                                            section,
-                                            instance_option))
-  {
-    GNUNET_free (instance_option);
-    return;
-  }
-  GNUNET_free (instance_option);
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_string (cfg,
-                                             section,
-                                             "PAYTO_URI",
-                                             &payto))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               section,
-                               "PAYTO_URI");
-    iic->ret = GNUNET_SYSERR;
-    return;
-  }
-
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_filename (cfg,
-                                               section,
-                                               "WIRE_RESPONSE",
-                                               &fn))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               section,
-                               "WIRE_RESPONSE");
-    GNUNET_free (payto);
-    iic->ret = GNUNET_SYSERR;
-    return;
-  }
-
-  /* Try loading existing JSON from file */
-  if (GNUNET_YES ==
-      GNUNET_DISK_file_test (fn))
-  {
-    json_error_t err;
-    char *url;
-
-    if (NULL ==
-        (j = json_load_file (fn,
-                             JSON_REJECT_DUPLICATES,
-                             &err)))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Failed to load JSON from `%s': %s at %d:%d\n",
-                  fn,
-                  err.text,
-                  err.line,
-                  err.column);
-      GNUNET_free (fn);
-      GNUNET_free (payto);
-      iic->ret = GNUNET_SYSERR;
-      return;
-    }
-    url = TALER_JSON_wire_to_payto (j);
-    if (NULL == url)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "URL missing in `%s', disabling account `%s'\n",
-                  fn,
-                  section);
-      GNUNET_free (fn);
-      GNUNET_free (payto);
-      iic->ret = GNUNET_SYSERR;
-      return;
-    }
-    if (0 != strcasecmp (url,
-                         payto))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "URL `%s' does not match configuration `%s', disabling 
account `%s'\n",
-                  url,
-                  payto,
-                  section);
-      GNUNET_free (fn);
-      GNUNET_free (payto);
-      GNUNET_free (url);
-      iic->ret = GNUNET_SYSERR;
-      return;
-    }
-    GNUNET_free (url);
-  }
-  else /* need to generate JSON */
-  {
-    struct GNUNET_HashCode salt;
-    char *salt_str;
-
-    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
-                                &salt,
-                                sizeof (salt));
-    salt_str = GNUNET_STRINGS_data_to_string_alloc (&salt,
-                                                    sizeof (salt));
-    j = json_pack ("{s:s, s:s}",
-                   "payto_uri", payto,
-                   "salt", salt_str);
-    GNUNET_free (salt_str);
-
-    /* Make sure every path component exists.  */
-    if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (fn))
-    {
-      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                "mkdir",
-                                fn);
-      GNUNET_free (fn);
-      GNUNET_free (payto);
-      json_decref (j);
-      iic->ret = GNUNET_SYSERR;
-      return;
-    }
-
-    if (0 != json_dump_file (j,
-                             fn,
-                             JSON_COMPACT | JSON_SORT_KEYS))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Failed to write hashed wire details to `%s'\n",
-                  fn);
-      GNUNET_free (fn);
-      GNUNET_free (payto);
-      json_decref (j);
-      iic->ret = GNUNET_SYSERR;
-      return;
-    }
-
-    if (GNUNET_OK ==
-        GNUNET_CONFIGURATION_get_value_string (cfg,
-                                               section,
-                                               "WIRE_FILE_MODE",
-                                               &wire_file_mode))
-    {
-      errno = 0;
-      mode_t mode = (mode_t) strtoul (wire_file_mode,
-                                      NULL,
-                                      8);
-      if (0 != errno)
-      {
-        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
-                                   section,
-                                   "WIRE_FILE_MODE",
-                                   "Must be octal number\n");
-        iic->ret = GNUNET_SYSERR;
-        GNUNET_free (fn);
-        return;
-      }
-      if (0 != chmod (fn, mode))
-      {
-        TALER_LOG_ERROR ("chmod failed on %s\n", fn);
-        iic->ret = GNUNET_SYSERR;
-        GNUNET_free (fn);
-        return;
-      }
-    }
-  }
-
-  GNUNET_free (fn);
-
-  if (GNUNET_OK !=
-      TALER_JSON_merchant_wire_signature_hash (j,
-                                               &jh_wire))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to hash wire input\n");
-    GNUNET_free (fn);
-    GNUNET_free (payto);
-    json_decref (j);
-    iic->ret = GNUNET_SYSERR;
-    return;
-  }
-
-  wm = GNUNET_new (struct WireMethod);
-  wm->wire_method = TALER_payto_get_method (payto);
-  GNUNET_free (payto);
-  GNUNET_asprintf (&instance_option,
-                   "ACTIVE_%s",
-                   mi->id);
-  wm->active = GNUNET_CONFIGURATION_get_value_yesno (cfg,
-                                                     section,
-                                                     instance_option);
-  GNUNET_free (instance_option);
-  if (GNUNET_YES == wm->active)
-    GNUNET_CONTAINER_DLL_insert (mi->wm_head,
-                                 mi->wm_tail,
-                                 wm);
-  else
-    GNUNET_CONTAINER_DLL_insert_tail (mi->wm_head,
-                                      mi->wm_tail,
-                                      wm);
-  wm->j_wire = j;
-  wm->h_wire = jh_wire;
-}
-
-
-/**
- * Callback that looks for 'instance-*' sections,
- * and populates accordingly each instance's data
- *
- * @param cls closure of type `struct IterateInstancesCls`
- * @section section name this callback gets
- */
-static void
-instances_iterator_cb (void *cls,
-                       const char *section)
-{
-  struct IterateInstancesCls *iic = cls;
-  char *token;
-  struct MerchantInstance *mi;
-  /* used as hashmap keys */
-  struct GNUNET_HashCode h_pk;
-  struct GNUNET_HashCode h_id;
-
-  if (0 != strncasecmp (section,
-                        "instance-",
-                        strlen ("instance-")))
-    return;
-  /** Get id **/
-  token = strrchr (section, '-');
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Extracted token: %s\n",
-              token + 1);
-  mi = GNUNET_new (struct MerchantInstance);
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_string (cfg,
-                                             section,
-                                             "NAME",
-                                             &mi->name))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               section,
-                               "NAME");
-    GNUNET_free (mi);
-    iic->ret = GNUNET_SYSERR;
-    return;
-  }
-
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_filename (cfg,
-                                               section,
-                                               "KEYFILE",
-                                               &mi->keyfile))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               section,
-                               "KEYFILE");
-    GNUNET_free (mi->name);
-    GNUNET_free (mi);
-    iic->ret = GNUNET_SYSERR;
-    return;
-  }
-  if (GNUNET_OK ==
-      GNUNET_CONFIGURATION_get_value_string (cfg,
-                                             section,
-                                             "TIP_EXCHANGE",
-                                             &mi->tip_exchange))
-  {
-    char *tip_reserves;
-
-    if (GNUNET_OK !=
-        GNUNET_CONFIGURATION_get_value_filename (cfg,
-                                                 section,
-                                                 "TIP_RESERVE_PRIV_FILENAME",
-                                                 &tip_reserves))
-    {
-      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                                 section,
-                                 "TIP_RESERVE_PRIV_FILENAME");
-      GNUNET_free (mi->keyfile);
-      GNUNET_free (mi->name);
-      GNUNET_free (mi);
-      iic->ret = GNUNET_SYSERR;
-      return;
-    }
-    if (GNUNET_OK !=
-        GNUNET_CRYPTO_eddsa_key_from_file (tip_reserves,
-                                           GNUNET_NO,
-                                           &mi->tip_reserve.eddsa_priv))
-    {
-      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->keyfile);
-      GNUNET_free (mi->name);
-      GNUNET_free (mi);
-      iic->ret = GNUNET_SYSERR;
-      return;
-    }
-    GNUNET_free (tip_reserves);
-  }
-
-  if (GNUNET_SYSERR ==
-      GNUNET_CRYPTO_eddsa_key_from_file (mi->keyfile,
-                                         GNUNET_YES,
-                                         &mi->privkey.eddsa_priv))
-  {
-    GNUNET_break (0);
-    GNUNET_free (mi->keyfile);
-    GNUNET_free (mi->name);
-    GNUNET_free (mi);
-    iic->ret = GNUNET_SYSERR;
-    return;
-  }
-  GNUNET_CRYPTO_eddsa_key_get_public (&mi->privkey.eddsa_priv,
-                                      &mi->pubkey.eddsa_pub);
-
-  mi->id = GNUNET_strdup (token + 1);
-  if (0 == strcasecmp ("default",
-                       mi->id))
-    iic->default_instance = GNUNET_YES;
-
-  GNUNET_CRYPTO_hash (mi->id,
-                      strlen (mi->id),
-                      &h_id);
-  if (GNUNET_OK !=
-      GNUNET_CONTAINER_multihashmap_put (by_id_map,
-                                         &h_id,
-                                         mi,
-                                         
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to put an entry into the 'by_id' hashmap\n");
-    iic->ret = GNUNET_SYSERR;
-    GNUNET_free (mi->keyfile);
-    GNUNET_free (mi->name);
-    GNUNET_free (mi);
-    return;
-  }
-  GNUNET_CRYPTO_hash (&mi->pubkey.eddsa_pub,
-                      sizeof (struct GNUNET_CRYPTO_EddsaPublicKey),
-                      &h_pk);
-  if (GNUNET_OK !=
-      GNUNET_CONTAINER_multihashmap_put (by_kpub_map,
-                                         &h_pk,
-                                         mi,
-                                         
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to put an entry into the 'by_kpub_map' hashmap\n");
-    GNUNET_assert (GNUNET_OK ==
-                   GNUNET_CONTAINER_multihashmap_remove (by_id_map,
-                                                         &h_id,
-                                                         mi));
-    iic->ret = GNUNET_SYSERR;
-    GNUNET_free (mi->keyfile);
-    GNUNET_free (mi->name);
-    GNUNET_free (mi);
-    return;
-  }
-
-  /* Initialize wireformats */
-  {
-    struct WireFormatIteratorContext wfic = {
-      .iic = iic,
-      .mi = mi
-    };
-
-    GNUNET_CONFIGURATION_iterate_sections (cfg,
-                                           &wireformat_iterator_cb,
-                                           &wfic);
-  }
-  if (NULL == mi->wm_head)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to load wire formats for instance `%s'\n",
-                mi->id);
-    iic->ret = GNUNET_SYSERR;
-  }
-
-}
-
-
 /**
  * Lookup a merchant instance by its instance ID.
  *
  * @param instance_id identifier of the instance to resolve
  * @return NULL if that instance is unknown to us
  */
-static struct MerchantInstance *
-lookup_instance (const char *instance_id)
+struct TMH_MerchantInstance *
+TMH_lookup_instance (const char *instance_id)
 {
   struct GNUNET_HashCode h_instance;
 
   if (NULL == instance_id)
     instance_id = "default";
-
   GNUNET_CRYPTO_hash (instance_id,
                       strlen (instance_id),
                       &h_instance);
@@ -1236,59 +671,41 @@ lookup_instance (const char *instance_id)
               instance_id);
   /* We're fine if that returns NULL, the calling routine knows how
      to handle that */
-  return GNUNET_CONTAINER_multihashmap_get (by_id_map,
+  return GNUNET_CONTAINER_multihashmap_get (TMH_by_id_map,
                                             &h_instance);
 }
 
 
 /**
- * Iterate over locations in config in order to populate
- * the location data.
+ * Add instance definition to our active set of instances.
  *
- * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors
+ * @param[in,out] mi merchant instance details to define
+ * @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already
  */
-static void
-iterate_locations (void)
-{
-  GNUNET_assert (NULL == default_locations);
-  default_locations = json_object ();
-  GNUNET_CONFIGURATION_iterate_sections (cfg,
-                                         &locations_iterator_cb,
-                                         NULL);
-}
-
-
-/**
- * Iterate over each merchant instance, in order to populate
- * each instance's own data
- *
- * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors
- *          (for example, if no "default" instance is defined)
- */
-static int
-iterate_instances (void)
+int
+TMH_add_instance (struct TMH_MerchantInstance *mi)
 {
-  struct IterateInstancesCls iic;
-
-  iic.default_instance = GNUNET_NO;
-  iic.ret = GNUNET_OK;
-  GNUNET_CONFIGURATION_iterate_sections (cfg,
-                                         &instances_iterator_cb,
-                                         &iic);
-
-  if (GNUNET_NO == iic.default_instance)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "No default merchant instance found\n");
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK != iic.ret)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "At least one instance was not successfully parsed\n");
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
+  struct GNUNET_HashCode h_instance;
+  const char *id;
+  int ret;
+
+  id = mi->settings.id;
+  if (NULL == id)
+    id = "default";
+  GNUNET_CRYPTO_hash (id,
+                      strlen (id),
+                      &h_instance);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Looking for by-id key %s of `%s' in hashmap\n",
+              GNUNET_h2s (&h_instance),
+              id);
+  ret = GNUNET_CONTAINER_multihashmap_put (TMH_by_id_map,
+                                           &h_instance,
+                                           mi,
+                                           
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
+  if (GNUNET_OK == ret)
+    mi->rc++;
+  return ret;
 }
 
 
@@ -1341,143 +758,411 @@ url_handler (void *cls,
              size_t *upload_data_size,
              void **con_cls)
 {
-  static struct TMH_RequestHandler handlers[] = {
-    /* Landing page, tell humans to go away. */
-    { "/", MHD_HTTP_METHOD_GET, "text/plain",
-      "Hello, I'm a merchant's Taler backend. This HTTP server is not for 
humans.\n",
-      0,
-      &TMH_MHD_handler_static_response, MHD_HTTP_OK },
-    { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
-    { "/track/transfer", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &MH_handler_track_transfer, MHD_HTTP_OK},
-    { "/track/transfer", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TMH_MHD_handler_static_response, MHD_HTTP_OK},
-    { "/track/transaction", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &MH_handler_track_transaction, MHD_HTTP_OK},
-    { "/track/transaction", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TMH_MHD_handler_static_response, MHD_HTTP_OK},
-    { "/history", MHD_HTTP_METHOD_GET, "text/plain",
-      "Only GET is allowed", 0,
-      &MH_handler_history, MHD_HTTP_OK},
-    { "/order", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &MH_handler_order_post, MHD_HTTP_OK },
-    { "/refund", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &MH_handler_refund_increase, MHD_HTTP_OK},
-    { "/tip-authorize", MHD_HTTP_METHOD_POST, "text/plain",
-      NULL, 0,
-      &MH_handler_tip_authorize, MHD_HTTP_OK},
-    { "/tip-query", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_tip_query, MHD_HTTP_OK},
-    { "/check-payment", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_check_payment, MHD_HTTP_OK},
-    { "/config", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_config, MHD_HTTP_OK},
-    {NULL, NULL, NULL, NULL, 0, 0 }
+  static struct TMH_RequestHandler private_handlers[] = {
+    /* GET /instances: */
+    {
+      .url_prefix = "/instances",
+      .method = MHD_HTTP_METHOD_GET,
+      .skip_instance = true,
+      .handler = &TMH_private_get_instances
+    },
+    /* GET /instances/$ID/: */
+    {
+      .url_prefix = "/",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_instances_ID
+    },
+    /* DELETE /instances/$ID/: */
+    {
+      .url_prefix = "/",
+      .method = MHD_HTTP_METHOD_DELETE,
+      .handler = &TMH_private_delete_instances_ID
+    },
+    /* PATCH /instances/$ID/: */
+    {
+      .url_prefix = "/",
+      .method = MHD_HTTP_METHOD_PATCH,
+      .handler = &TMH_private_patch_instances_ID,
+      /* allow instance data of up to 8 MB, that should be plenty;
+         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+         would require further changes to the allocation logic
+         in the code... */
+      .max_upload = 1024 * 1024 * 8
+    },
+    /* POST /instances: */
+    {
+      .url_prefix = "/instances",
+      .method = MHD_HTTP_METHOD_POST,
+      .skip_instance = true,
+      .handler = &TMH_private_post_instances,
+      /* allow instance data of up to 8 MB, that should be plenty;
+         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+         would require further changes to the allocation logic
+         in the code... */
+      .max_upload = 1024 * 1024 * 8
+    },
+    /* GET /products: */
+    {
+      .url_prefix = "/products",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_products
+    },
+    /* POST /products: */
+    {
+      .url_prefix = "/products",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_private_post_products,
+      /* allow product data of up to 8 MB, that should be plenty;
+         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+         would require further changes to the allocation logic
+         in the code... */
+      .max_upload = 1024 * 1024 * 8
+    },
+    /* GET /products/$ID/: */
+    {
+      .url_prefix = "/products/",
+      .method = MHD_HTTP_METHOD_GET,
+      .have_id_segment = true,
+      .handler = &TMH_private_get_products_ID
+    },
+    /* DELETE /products/$ID/: */
+    {
+      .url_prefix = "/products/",
+      .method = MHD_HTTP_METHOD_DELETE,
+      .have_id_segment = true,
+      .handler = &TMH_private_delete_products_ID
+    },
+    /* PATCH /products/$ID/: */
+    {
+      .url_prefix = "/products/",
+      .method = MHD_HTTP_METHOD_PATCH,
+      .have_id_segment = true,
+      .handler = &TMH_private_patch_products_ID,
+      /* allow product data of up to 8 MB, that should be plenty;
+         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+         would require further changes to the allocation logic
+         in the code... */
+      .max_upload = 1024 * 1024 * 8
+    },
+    /* POST /products/$ID/lock: */
+    {
+      .url_prefix = "/products/",
+      .url_suffix = "lock",
+      .method = MHD_HTTP_METHOD_POST,
+      .have_id_segment = true,
+      .handler = &TMH_private_post_products_ID_lock,
+      /* the body should be pretty small, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* POST /orders: */
+    {
+      .url_prefix = "/orders",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_private_post_orders,
+      /* allow contracts of up to 8 MB, that should be plenty;
+         note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
+         would require further changes to the allocation logic
+         in the code... */
+      .max_upload = 1024 * 1024 * 8
+    },
+    /* GET /orders/$ID: */
+    {
+      .url_prefix = "/orders/",
+      .method = MHD_HTTP_METHOD_GET,
+      .have_id_segment = true,
+      .handler = &TMH_private_get_orders_ID
+    },
+    /* GET /orders: */
+    {
+      .url_prefix = "/orders",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_orders
+    },
+    /* POST /orders/$ID/refund: */
+    {
+      .url_prefix = "/orders/",
+      .url_suffix = "refund",
+      .method = MHD_HTTP_METHOD_POST,
+      .have_id_segment = true,
+      .handler = &TMH_private_post_orders_ID_refund,
+      /* the body should be pretty small, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* DELETE /orders/$ID: */
+    {
+      .url_prefix = "/orders/",
+      .method = MHD_HTTP_METHOD_DELETE,
+      .have_id_segment = true,
+      .handler = &TMH_private_delete_orders_ID
+    },
+    /* POST /reserves: */
+    {
+      .url_prefix = "/reserves",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_private_post_reserves,
+      /* the body should be pretty small, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* DELETE /reserves/$ID: */
+    {
+      .url_prefix = "/reserves/",
+      .have_id_segment = true,
+      .method = MHD_HTTP_METHOD_DELETE,
+      .handler = &TMH_private_delete_reserves_ID
+    },
+    /* POST /reserves/$ID/authorize-tip: */
+    {
+      .url_prefix = "/reserves/",
+      .url_suffix = "authorize-tip",
+      .have_id_segment = true,
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_private_post_reserves_ID_authorize_tip,
+      /* the body should be pretty small, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* POST /tips: */
+    {
+      .url_prefix = "/tips",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_private_post_tips,
+      /* the body should be pretty small, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* GET /tips: */
+    {
+      .url_prefix = "/tips",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_tips
+    },
+    /* GET /tips/$ID: */
+    {
+      .url_prefix = "/tips/",
+      .method = MHD_HTTP_METHOD_GET,
+      .have_id_segment = true,
+      .handler = &TMH_private_get_tips_ID
+    },
+    /* GET /reserves: */
+    {
+      .url_prefix = "/reserves",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_reserves
+    },
+    /* GET /reserves: */
+    {
+      .url_prefix = "/reserves/",
+      .have_id_segment = true,
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_reserves_ID
+    },
+    /* POST /transfers: */
+    {
+      .url_prefix = "/transfers",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_private_post_transfers,
+      /* the body should be pretty small, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* GET /transfers: */
+    {
+      .url_prefix = "/transfers",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler = &TMH_private_get_transfers
+    },
+    {
+      NULL
+    }
   };
   static struct TMH_RequestHandler public_handlers[] = {
-    { "/pay", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &MH_handler_pay, MHD_HTTP_OK },
-    { "/proposal", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_proposal_lookup, MHD_HTTP_OK },
-    { "/tip-pickup", MHD_HTTP_METHOD_POST, "text/plain",
-      NULL, 0,
-      &MH_handler_tip_pickup, MHD_HTTP_OK },
-    { "/refund", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_refund_lookup, MHD_HTTP_OK },
-    { "/tip-pickup", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_tip_pickup_get, MHD_HTTP_OK },
-    { "/poll-payment", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_poll_payment, MHD_HTTP_OK},
-    { "/config", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &MH_handler_config, MHD_HTTP_OK},
-    {NULL, NULL, NULL, NULL, 0, 0 }
+    {
+      .url_prefix = "/",
+      .method = MHD_HTTP_METHOD_GET,
+      .mime_type = "text/plain",
+      .skip_instance = true,
+      .data = "This is a GNU Taler merchant backend. See 
https://taler.net/.\n";,
+      .data_size = strlen (
+        "This is a GNU Taler merchant backend. See https://taler.net/.\n";),
+      .handler = &TMH_MHD_handler_static_response,
+      .response_code = MHD_HTTP_OK
+    },
+    {
+      .url_prefix = "/agpl",
+      .method = MHD_HTTP_METHOD_GET,
+      .skip_instance = true,
+      .handler = &TMH_MHD_handler_agpl_redirect
+    },
+    {
+      .url_prefix = "/config",
+      .method = MHD_HTTP_METHOD_GET,
+      .skip_instance = true,
+      .handler = &MH_handler_config
+    },
+    /* POST /orders/$ID/abort: */
+    {
+      .url_prefix = "/orders/",
+      .have_id_segment = true,
+      .url_suffix = "abort",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_post_orders_ID_abort,
+      /* wallet may give us many coins to sign, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* POST /orders/$ID/claim: */
+    {
+      .url_prefix = "/orders/",
+      .have_id_segment = true,
+      .url_suffix = "claim",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_post_orders_ID_claim,
+      /* the body should be pretty small, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* POST /orders/$ID/pay: */
+    {
+      .url_prefix = "/orders/",
+      .have_id_segment = true,
+      .url_suffix = "pay",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler = &TMH_post_orders_ID_pay,
+      /* wallet may give us many coins to sign, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    /* GET /orders/$ID: */
+    {
+      .url_prefix = "/orders/",
+      .method = MHD_HTTP_METHOD_GET,
+      .have_id_segment = true,
+      .handler = &TMH_get_orders_ID
+    },
+    /* GET /tips/$ID: */
+    {
+      .url_prefix = "/tips/",
+      .method = MHD_HTTP_METHOD_GET,
+      .have_id_segment = true,
+      .handler = &TMH_get_tips_ID
+    },
+    /* POST /tips/$ID/pickup: */
+    {
+      .url_prefix = "/tips/",
+      .method = MHD_HTTP_METHOD_POST,
+      .have_id_segment = true,
+      .url_suffix = "pickup",
+      .handler = &TMH_post_tips_ID_pickup,
+      /* wallet may give us many coins to sign, allow 1 MB of upload
+         to set a conservative bound for sane wallets */
+      .max_upload = 1024 * 1024
+    },
+    {
+      NULL
+    }
   };
   static struct TMH_RequestHandler h404 = {
-    "", NULL, "text/html",
-    "<html><title>404: not found</title><body>404: not found</body></html>", 0,
-    &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
+    .mime_type = "application/json",
+    .data = "{\"code\":10}",
+    .data_size = strlen ("{\"code\":10}"),
+    .handler = &TMH_MHD_handler_static_response,
+    .response_code = MHD_HTTP_NOT_FOUND
   };
-
-  struct TM_HandlerContext *hc = *con_cls;
-  struct GNUNET_AsyncScopeId aid;
-  const char *correlation_id = NULL;
-  struct MerchantInstance *instance;
-  const char *effective_url;
-  /* Is a publicly facing endpoint being requested? */
-  int is_public;
-  /* Matching URL found, but maybe method doesn't match */
-  int url_found = GNUNET_NO;
-  MHD_RESULT ret;
-  struct TMH_RequestHandler *selected_handler = NULL;
+  struct TMH_HandlerContext *hc = *con_cls;
+  struct TMH_RequestHandler *handlers;
+  bool use_private = false;
 
   (void) cls;
   (void) version;
-  if (NULL == hc)
+  if (NULL != hc)
+  {
+    GNUNET_assert (NULL != hc->rh);
+    GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
+    if ( (hc->has_body) &&
+         (NULL == hc->request_body) )
+    {
+      int res;
+
+      if ( (hc->total_upload + *upload_data_size < hc->total_upload) ||
+           (hc->total_upload + *upload_data_size > hc->rh->max_upload) )
+      {
+        /* Client exceeds upload limit. Should _usually_ be checked earlier
+           when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with
+           chunked encoding an uploader MAY have ommitted this, and thus
+           not permitted us to check on time. In this case, we just close
+           the connection once it exceeds our limit (instead of waiting
+           for the upload to complete and then fail). This could theoretically
+           cause some clients to retry, alas broken or malicious clients
+           are likely to retry anyway, so little we can do about it, and
+           failing earlier seems the best option here.  *///
+        GNUNET_break_op (0);
+        return MHD_NO;
+      }
+      hc->total_upload += *upload_data_size;
+      res = TALER_MHD_parse_post_json (connection,
+                                       &hc->json_parse_context,
+                                       upload_data,
+                                       upload_data_size,
+                                       &hc->request_body);
+      if (GNUNET_SYSERR == res)
+        return MHD_NO;
+      /* A error response was already generated */
+      if ( (GNUNET_NO == res) ||
+           /* or, need more data to accomplish parsing */
+           (NULL == hc->request_body) )
+        return MHD_YES;
+    }
+    return hc->rh->handler (hc->rh,
+                            connection,
+                            hc);
+  }
+  hc = GNUNET_new (struct TMH_HandlerContext);
+  *con_cls = hc;
+  GNUNET_async_scope_fresh (&hc->async_scope_id);
+  GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
+  hc->url = url;
   {
-    GNUNET_async_scope_fresh (&aid);
-    /* We only read the correlation ID on the first callback for every client 
*/
+    const char *correlation_id;
+
     correlation_id = MHD_lookup_connection_value (connection,
                                                   MHD_HEADER_KIND,
                                                   "Taler-Correlation-Id");
-    if ((NULL != correlation_id) &&
-        (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)))
+    if ( (NULL != correlation_id) &&
+         (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)) )
     {
       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "illegal incoming correlation ID\n");
+                  "Illegal incoming correlation ID\n");
       correlation_id = NULL;
     }
+    if (NULL != correlation_id)
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Handling request for (%s) URL '%s', correlation_id=%s\n",
+                  method,
+                  url,
+                  correlation_id);
+    else
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Handling request (%s) for URL '%s'\n",
+                  method,
+                  url);
   }
-  else
-  {
-    aid = hc->async_scope_id;
-  }
-
-  GNUNET_SCHEDULER_begin_async_scope (&aid);
-
-  if (NULL != correlation_id)
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Handling request for (%s) URL '%s', correlation_id=%s\n",
-                method,
-                url,
-                correlation_id);
-  else
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Handling request (%s) for URL '%s'\n",
-                method,
-                url);
 
-  effective_url = url;
+  if (0 == strcasecmp (method,
+                       MHD_HTTP_METHOD_HEAD))
+    method = MHD_HTTP_METHOD_GET; /* MHD will deal with the rest */
 
   {
-    const char *public_prefix = "/public/";
+    const char *private_prefix = "/private/";
 
-    if (0 == strncmp (effective_url,
-                      public_prefix,
-                      strlen (public_prefix)))
+    if (0 == strncmp (url,
+                      private_prefix,
+                      strlen (private_prefix)))
     {
-      is_public = GNUNET_YES;
-      effective_url = effective_url + strlen (public_prefix) - 1;
-    }
-    else
-    {
-      is_public = GNUNET_NO;
+      use_private = true;
+      url += strlen (private_prefix) - 1;
     }
   }
 
@@ -1487,127 +1172,261 @@ url_handler (void *cls,
   {
     const char *instance_prefix = "/instances/";
 
-    if (0 == strncmp (effective_url,
+    if (0 == strncmp (url,
                       instance_prefix,
                       strlen (instance_prefix)))
     {
       /* url starts with "/instances/" */
-      const char *istart = effective_url + strlen (instance_prefix);
+      const char *istart = url + strlen (instance_prefix);
       const char *slash = strchr (istart, '/');
       char *instance_id;
 
       if (NULL == slash)
-      {
-        return TMH_MHD_handler_static_response (&h404,
-                                                connection,
-                                                con_cls,
-                                                upload_data,
-                                                upload_data_size,
-                                                NULL);
-      }
-      instance_id = GNUNET_strndup (istart,
-                                    slash - istart);
-      instance = lookup_instance (instance_id);
+        instance_id = GNUNET_strdup (istart);
+      else
+        instance_id = GNUNET_strndup (istart,
+                                      slash - istart);
+      hc->instance = TMH_lookup_instance (instance_id);
       GNUNET_free (instance_id);
-      effective_url = slash;
+      if (NULL == slash)
+        url = "";
+      else
+        url = slash;
+    }
+    else
+    {
+      /* use 'default' */
+      hc->instance = TMH_lookup_instance (NULL);
+    }
+    if (NULL != hc->instance)
+      hc->instance->rc++;
+  }
+
+  {
+    const char *private_prefix = "/private/";
+
+    if (0 == strncmp (url,
+                      private_prefix,
+                      strlen (private_prefix)))
+    {
+      handlers = private_handlers;
+      url += strlen (private_prefix) - 1;
     }
     else
     {
-      instance = lookup_instance (NULL);
+      handlers = (use_private) ? private_handlers : public_handlers;
     }
   }
-  if (NULL == instance)
-    return TALER_MHD_reply_json_pack (connection,
-                                      MHD_HTTP_NOT_FOUND,
-                                      "{s:I, s:s}",
-                                      "code",
-                                      (json_int_t) TALER_EC_INSTANCE_UNKNOWN,
-                                      "error",
-                                      "merchant instance unknown");
-
-  if (GNUNET_NO == is_public)
+  if (0 == strcmp (url,
+                   ""))
+    url = "/"; /* code below does not like empty string */
+
   {
-    for (unsigned int i = 0; NULL != handlers[i].url; i++)
+    /* Matching URL found, but maybe method doesn't match */
+    size_t prefix_strlen; /* i.e. 8 for "/orders/", or 7 for "/config" */
+    const char *infix_url = NULL; /* i.e. "$ORDER_ID", no '/'-es */
+    size_t infix_strlen = 0; /* number of characters in infix_url */
+    const char *suffix_url = NULL; /* i.e. "/refund", includes '/' at the 
beginning */
+    size_t suffix_strlen = 0; /* number of characters in suffix_url */
+
     {
-      struct TMH_RequestHandler *rh = &handlers[i];
+      const char *slash;
 
-      if ( (0 != strcasecmp (effective_url, rh->url)) )
-        continue;
-      url_found = GNUNET_YES;
-      if (0 == strcasecmp (method,
-                           MHD_HTTP_METHOD_OPTIONS))
+      slash = strchr (&url[1], '/');
+      if (NULL == slash)
       {
-        return TALER_MHD_reply_cors_preflight (connection);
+        prefix_strlen = strlen (url);
+      }
+      else
+      {
+        prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */
+        infix_url = slash + 1;
+        slash = strchr (&infix_url[1], '/');
+        if (NULL == slash)
+        {
+          infix_strlen = strlen (infix_url);
+        }
+        else
+        {
+          infix_strlen = slash - infix_url;
+          suffix_url = slash + 1; /* skip the '/' */
+          suffix_strlen = strlen (suffix_url);
+        }
+        hc->infix = GNUNET_strndup (infix_url,
+                                    infix_strlen);
       }
-      if ( (rh->method != NULL) &&
-           (0 != strcasecmp (method, rh->method)) )
-        continue;
-      selected_handler = rh;
-      break;
     }
-  }
 
-  if (NULL == selected_handler)
-  {
-    for (unsigned int i = 0; NULL != public_handlers[i].url; i++)
     {
-      struct TMH_RequestHandler *rh = &public_handlers[i];
+      bool url_found = false;
 
-      if ( (0 != strcasecmp (effective_url, rh->url)) )
-        continue;
-      url_found = GNUNET_YES;
-      if (0 == strcasecmp (method,
-                           MHD_HTTP_METHOD_OPTIONS))
+      for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
       {
-        return TALER_MHD_reply_cors_preflight (connection);
+        struct TMH_RequestHandler *rh = &handlers[i];
+
+        if ( (prefix_strlen != strlen (rh->url_prefix)) ||
+             (0 != memcmp (url,
+                           rh->url_prefix,
+                           prefix_strlen)) )
+          continue;
+        if (GNUNET_NO == rh->have_id_segment)
+        {
+          if (NULL != suffix_url)
+            continue; /* too many segments to match */
+          if ( (NULL == infix_url)
+               ^ (NULL == rh->url_suffix) )
+            continue; /* suffix existence missmatch */
+          if ( (NULL != infix_url) &&
+               ( (infix_strlen != strlen (rh->url_suffix)) ||
+                 (0 != memcmp (infix_url,
+                               rh->url_suffix,
+                               infix_strlen)) ) )
+            continue; /* cannot use infix as suffix: content missmatch */
+        }
+        else
+        {
+          if ( (NULL == infix_url)
+               ^ (GNUNET_NO == rh->have_id_segment) )
+            continue; /* infix existence missmatch */
+          if ( ( (NULL == suffix_url)
+                 ^ (NULL == rh->url_suffix) ) )
+            continue; /* suffix existence missmatch */
+          if ( (NULL != suffix_url) &&
+               ( (suffix_strlen != strlen (rh->url_suffix)) ||
+                 (0 != memcmp (suffix_url,
+                               rh->url_suffix,
+                               suffix_strlen)) ) )
+            continue; /* suffix content missmatch */
+        }
+        url_found = true;
+        if (0 == strcasecmp (method,
+                             MHD_HTTP_METHOD_OPTIONS))
+        {
+          return TALER_MHD_reply_cors_preflight (connection);
+        }
+        if ( (rh->method != NULL) &&
+             (0 != strcasecmp (method,
+                               rh->method)) )
+          continue;
+        hc->rh = rh;
+        break;
       }
-      if ( (rh->method != NULL) && (0 != strcasecmp (method, rh->method)) )
-        continue;
-      selected_handler = rh;
-      break;
+      if ( (NULL == hc->rh) &&
+           (url_found) )
+        return TALER_MHD_reply_json_pack (connection,
+                                          MHD_HTTP_METHOD_NOT_ALLOWED,
+                                          "{s:s}",
+                                          "error",
+                                          "method not allowed");
+      if (NULL == hc->rh)
+        return TMH_MHD_handler_static_response (&h404,
+                                                connection,
+                                                hc);
     }
   }
-
-  if (NULL == selected_handler)
+  /* At this point, we must have found a handler */
+  GNUNET_assert (NULL != hc->rh);
+  if ( (NULL == hc->instance) &&
+       (GNUNET_YES != hc->rh->skip_instance) )
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_INSTANCE_UNKNOWN,
+                                       "merchant instance unknown");
+  hc->has_body = ( (0 == strcasecmp (method,
+                                     MHD_HTTP_METHOD_POST)) ||
+                   (0 == strcasecmp (method,
+                                     MHD_HTTP_METHOD_PATCH)) );
+  if (hc->has_body)
   {
-    if (GNUNET_YES == url_found)
+    const char *cl;
+
+    /* Maybe check for maximum upload size
+       and refuse requests if they are just too big. */
+    cl = MHD_lookup_connection_value (connection,
+                                      MHD_HEADER_KIND,
+                                      MHD_HTTP_HEADER_CONTENT_LENGTH);
+    if (NULL != cl)
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "invalid request: method '%s' for '%s' not allowed\n",
-                  method,
-                  url);
-      return TALER_MHD_reply_json_pack (connection,
-                                        MHD_HTTP_METHOD_NOT_ALLOWED,
-                                        "{s:s}",
-                                        "error",
-                                        "method not allowed");
+      unsigned long long cv;
+      size_t mul = hc->rh->max_upload;
+
+      if (0 == mul)
+        mul = DEFAULT_MAX_UPLOAD_SIZE;
+      if (1 != sscanf (cl,
+                       "%llu",
+                       &cv))
+      {
+        /* Not valid HTTP request, just close connection. */
+        GNUNET_break_op (0);
+        return MHD_NO;
+      }
+      if (cv > mul)
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_PAYLOAD_TOO_LARGE,
+                                           TALER_EC_UPLOAD_EXCEEDS_LIMIT,
+                                           "upload exceeds limit");
+      }
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "invalid request: URL '%s' not handled\n",
-                url);
-    return TMH_MHD_handler_static_response (&h404,
-                                            connection,
-                                            con_cls,
-                                            upload_data,
-                                            upload_data_size,
-                                            instance);
+    GNUNET_break (NULL == hc->request_body); /* can't have it already */
+    return MHD_YES; /* proceed with upload */
   }
+  return hc->rh->handler (hc->rh,
+                          connection,
+                          hc);
+}
 
-  ret = selected_handler->handler (selected_handler,
-                                   connection,
-                                   con_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   instance);
-  hc = *con_cls;
-  if (NULL != hc)
+
+/**
+ * Function called during startup to add all known instances to our
+ * hash map in memory for faster lookups when we receive requests.
+ *
+ * @param cls closure, NULL, unused
+ * @param merchant_pub public key of the instance
+ * @param merchant_priv private key of the instance, NULL if not available
+ * @param is detailed configuration settings for the instance
+ * @param accounts_length length of the @a accounts array
+ * @param accounts list of accounts of the merchant
+ */
+static void
+add_instance_cb (void *cls,
+                 const struct TALER_MerchantPublicKeyP *merchant_pub,
+                 const struct TALER_MerchantPrivateKeyP *merchant_priv,
+                 const struct TALER_MERCHANTDB_InstanceSettings *is,
+                 unsigned int accounts_length,
+                 const struct TALER_MERCHANTDB_AccountDetails accounts[])
+{
+  struct TMH_MerchantInstance *mi;
+
+  (void) cls;
+  GNUNET_assert (NULL != merchant_priv);
+  mi = GNUNET_new (struct TMH_MerchantInstance);
+  mi->settings = *is;
+  mi->settings.id = GNUNET_strdup (mi->settings.id);
+  mi->settings.name = GNUNET_strdup (mi->settings.name);
+  mi->settings.address = json_incref (mi->settings.address);
+  mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
+  mi->merchant_priv = *merchant_priv;
+  mi->merchant_pub = *merchant_pub;
+  for (unsigned int i = 0; i<accounts_length; i++)
   {
-    hc->rh = selected_handler;
-    /* Store the async context ID, so we can restore it if
-     * we get another callback for this request. */
-    hc->async_scope_id = aid;
+    const struct TALER_MERCHANTDB_AccountDetails *acc = &accounts[i];
+    struct TMH_WireMethod *wm;
+
+    wm = GNUNET_new (struct TMH_WireMethod);
+    wm->h_wire = acc->h_wire;
+    wm->j_wire = json_pack ("{s:s, s:s}",
+                            "payto_uri", acc->payto_uri,
+                            "salt", GNUNET_JSON_from_data_auto (&acc->salt));
+    wm->wire_method = TALER_payto_get_method (acc->payto_uri);
+    wm->active = acc->active;
+    GNUNET_CONTAINER_DLL_insert (mi->wm_head,
+                                 mi->wm_tail,
+                                 wm);
   }
-  return ret;
+  GNUNET_assert (GNUNET_OK ==
+                 TMH_add_instance (mi));
 }
 
 
@@ -1632,25 +1451,43 @@ run (void *cls,
   (void) cls;
   (void) args;
   (void) cfgfile;
+  cfg = config;
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Starting taler-merchant-httpd\n");
   go = TALER_MHD_GO_NONE;
-  if (TMH_merchant_connection_close)
+  if (merchant_connection_close)
     go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
   TALER_MHD_setup (go);
 
   result = GNUNET_SYSERR;
   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                  NULL);
+  resume_timeout_heap
+    = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
+  payment_trigger_map
+    = GNUNET_CONTAINER_multihashmap_create (16,
+                                            GNUNET_YES);
   if (GNUNET_OK !=
-      TALER_config_get_currency (config,
+      TALER_config_get_currency (cfg,
                                  &TMH_currency))
   {
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (cfg,
+                                           "merchant",
+                                           "LEGAL_PRESERVATION",
+                                           &TMH_legal_expiration))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "merchant",
+                               "LEGAL_PRESERVATION");
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
   if (GNUNET_YES ==
-      GNUNET_CONFIGURATION_get_value_yesno (config,
+      GNUNET_CONFIGURATION_get_value_yesno (cfg,
                                             "merchant",
                                             "FORCE_AUDIT"))
     TMH_force_audit = GNUNET_YES;
@@ -1666,105 +1503,37 @@ run (void *cls,
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
-
   if (NULL ==
-      (by_id_map = GNUNET_CONTAINER_multihashmap_create (1,
-                                                         GNUNET_NO)))
+      (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (1,
+                                                             GNUNET_NO)))
   {
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
-
   if (NULL ==
-      (by_kpub_map = GNUNET_CONTAINER_multihashmap_create (1,
-                                                           GNUNET_NO)))
+      (TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
   {
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
-
-  if (GNUNET_SYSERR ==
-      GNUNET_CONFIGURATION_get_value_time (config,
-                                           "merchant",
-                                           "WIRE_TRANSFER_DELAY",
-                                           &default_wire_transfer_delay))
+  /* load instances */
   {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "merchant",
-                               "WIRE_TRANSFER_DELAY");
-    GNUNET_SCHEDULER_shutdown ();
-    return;
-  }
-
-  if (GNUNET_SYSERR ==
-      GNUNET_CONFIGURATION_get_value_time (config,
-                                           "merchant",
-                                           "DEFAULT_PAY_DEADLINE",
-                                           &default_pay_deadline))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "merchant",
-                               "DEFAULT_PAY_DEADLINE");
-    GNUNET_SCHEDULER_shutdown ();
-    return;
-  }
-
-  if (GNUNET_OK !=
-      TALER_config_get_amount (config,
-                               "merchant",
-                               "DEFAULT_MAX_WIRE_FEE",
-                               &default_max_wire_fee))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "merchant",
-                               "DEFAULT_MAX_WIRE_FEE");
-    GNUNET_SCHEDULER_shutdown ();
-    return;
-  }
-
-  if (GNUNET_OK !=
-      TALER_config_get_amount (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;
-  }
+    enum GNUNET_DB_QueryStatus qs;
 
-  if (GNUNET_OK !=
-      GNUNET_CONFIGURATION_get_value_number (config,
-                                             "merchant",
-                                             "DEFAULT_WIRE_FEE_AMORTIZATION",
-                                             &default_wire_fee_amortization))
-  {
-    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
-                               "merchant",
-                               "DEFAULT_WIRE_FEE_AMORTIZATION");
-    GNUNET_SCHEDULER_shutdown ();
-    return;
-  }
-
-  cfg = GNUNET_CONFIGURATION_dup (config);
-  if (GNUNET_OK !=
-      iterate_instances ())
-  {
-    GNUNET_SCHEDULER_shutdown ();
-    return;
-  }
-  iterate_locations ();
-
-  if (NULL ==
-      (db = TALER_MERCHANTDB_plugin_load (cfg)))
-  {
-    GNUNET_SCHEDULER_shutdown ();
-    return;
+    qs = TMH_db->lookup_instances (TMH_db->cls,
+                                   true,
+                                   &add_instance_cb,
+                                   NULL);
+    if (0 > qs)
+    {
+      GNUNET_break (0);
+      GNUNET_SCHEDULER_shutdown ();
+      return;
+    }
   }
-
-  fh = TALER_MHD_bind (config,
+  /* start watching reserves */
+  TMH_RESERVES_init ();
+  fh = TALER_MHD_bind (cfg,
                        "merchant",
                        &port);
   if ( (0 == port) &&
@@ -1773,11 +1542,6 @@ run (void *cls,
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
-  resume_timeout_heap
-    = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
-  payment_trigger_map
-    = GNUNET_CONTAINER_multihashmap_create (16,
-                                            GNUNET_YES);
   mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
                           port,
                           NULL, NULL,
@@ -1805,7 +1569,7 @@ run (void *cls,
  *
  * @param argc number of arguments from the command line
  * @param argv command line arguments
- * @return 0 ok, 1 on error
+ * @return 0 ok, non-zero on error
  */
 int
 main (int argc,
@@ -1815,12 +1579,11 @@ main (int argc,
     GNUNET_GETOPT_option_flag ('C',
                                "connection-close",
                                "force HTTP connections to be closed after each 
request",
-                               &TMH_merchant_connection_close),
+                               &merchant_connection_close),
     GNUNET_GETOPT_option_timetravel ('T',
                                      "timetravel"),
     GNUNET_GETOPT_OPTION_END
   };
-
   if (GNUNET_OK !=
       GNUNET_PROGRAM_run (argc, argv,
                           "taler-merchant-httpd",
diff --git a/src/backend/taler-merchant-httpd.h 
b/src/backend/taler-merchant-httpd.h
index 28a0f2e..f4c1b64 100644
--- a/src/backend/taler-merchant-httpd.h
+++ b/src/backend/taler-merchant-httpd.h
@@ -23,7 +23,6 @@
 
 #include "platform.h"
 #include "taler_merchantdb_lib.h"
-#include <microhttpd.h>
 #include <taler/taler_mhd_lib.h>
 #include <gnunet/gnunet_mhd_compat.h>
 
@@ -39,17 +38,17 @@
 /**
  * Supported wire method.  Kept in a DLL.
  */
-struct WireMethod
+struct TMH_WireMethod
 {
   /**
    * Next entry in DLL.
    */
-  struct WireMethod *next;
+  struct TMH_WireMethod *next;
 
   /**
    * Previous entry in DLL.
    */
-  struct WireMethod *prev;
+  struct TMH_WireMethod *prev;
 
   /**
    * Which wire method / payment target identifier is @e j_wire using?
@@ -69,80 +68,130 @@ struct WireMethod
   /**
    * Is this wire method active (should it be included in new contracts)?
    */
-  int active;
+  bool active;
+
+  /**
+   * Are we currently in a transaction to delete this account?
+   */
+  bool deleting;
 
 };
 
 
+/**
+ * A pending GET /orders request that is in long polling mode.
+ */
+struct TMH_PendingOrder;
+
+
 /**
  * Information that defines a merchant "instance". That way, a single
  * backend can account for several merchants, as used to do in donation
  * shops
  */
-struct MerchantInstance
+struct TMH_MerchantInstance
 {
 
   /**
-   * Instance's mnemonic identifier. This value lives as long as
-   * the configuration is kept in memory, as it's as substring of
-   * a section name
-   */
-  char *id;
-
-  /**
-   * Legal name of the merchant.
+   * Next entry in DLL.
    */
-  char *name;
+  struct TMH_WireMethod *wm_head;
 
   /**
-   * File holding the merchant's private key
+   * Previous entry in DLL.
    */
-  char *keyfile;
+  struct TMH_WireMethod *wm_tail;
 
   /**
-   * Next entry in DLL.
+   * Head of DLL of long-polling GET /orders requests of this instance.
    */
-  struct WireMethod *wm_head;
+  struct TMH_PendingOrder *po_head;
 
   /**
-   * Previous entry in DLL.
+   * Tail of DLL of long-polling GET /orders requests of this instance.
    */
-  struct WireMethod *wm_tail;
+  struct TMH_PendingOrder *po_tail;
 
   /**
-   * Merchant's private key
+   * Merchant's private key.
    */
-  struct TALER_MerchantPrivateKeyP privkey;
+  struct TALER_MerchantPrivateKeyP merchant_priv;
 
   /**
    * Merchant's public key
    */
-  struct TALER_MerchantPublicKeyP pubkey;
+  struct TALER_MerchantPublicKeyP merchant_pub;
 
   /**
-   * Exchange this instance uses for tipping, NULL if tipping
-   * is not supported.
+   * General settings for an instance.
    */
-  char *tip_exchange;
+  struct TALER_MERCHANTDB_InstanceSettings settings;
 
   /**
-   * What is the private key of the reserve used for signing tips by this 
exchange?
-   * Only valid if @e tip_exchange is non-null.
+   * Reference counter on this structure. Only destroyed if the
+   * counter hits zero.
    */
-  struct TALER_ReservePrivateKeyP tip_reserve;
+  unsigned int rc;
 };
 
 
 /**
  * @brief Struct describing an URL and the handler for it.
+ *
+ * The overall URL is always @e url_prefix, optionally followed by the
+ * id_segment, which is optionally followed by the @e url_suffix. It is NOT
+ * allowed for the @e url_prefix to be directly followed by the @e url_suffix.
+ * A @e url_suffix SHOULD only be used with a @e method of 
#MHD_HTTP_METHOD_POST.
+ */
+struct TMH_RequestHandler;
+
+/**
+ * This information is stored in the "connection_cls" of MHD for
+ * every request that we process.
+ * Individual handlers can evaluate tis memebers and
+ * are allowed to update @e cc and @e ctx to store and clean up
+ * handler-specific data.
+ */
+struct TMH_HandlerContext;
+
+
+/**
+ * @brief Struct describing an URL and the handler for it.
+ *
+ * The overall URL is always @e url_prefix, optionally followed by the
+ * id_segment, which is optionally followed by the @e url_suffix. It is NOT
+ * allowed for the @e url_prefix to be directly followed by the @e url_suffix.
+ * A @e url_suffix SHOULD only be used with a @e method of 
#MHD_HTTP_METHOD_POST.
  */
 struct TMH_RequestHandler
 {
 
   /**
-   * URL the handler is for.
+   * URL prefix the handler is for, includes the '/',
+   * so "/orders" or "/products".  Does *not* include
+   * "/private", that is controlled by the array in which
+   * the handler is defined.  Must not contain any
+   * '/' except for the leading '/'.
    */
-  const char *url;
+  const char *url_prefix;
+
+  /**
+   * Does this request include an identifier segment
+   * (product_id, reserve_pub, order_id, tip_id) in the
+   * second segment?
+   */
+  bool have_id_segment;
+
+  /**
+   * Does this request handler work without an instance?
+   */
+  bool skip_instance;
+
+  /**
+   * URL suffix the handler is for, excludes the '/',
+   * so "pay" or "claim", not "/pay".
+   */
+  const char *url_suffix;
 
   /**
    * Method the handler is for, NULL for "all".
@@ -155,84 +204,120 @@ struct TMH_RequestHandler
   const char *mime_type;
 
   /**
-   * Raw data for the @e handler
+   * Raw data for the @e handler (can be NULL).
    */
   const void *data;
 
   /**
-   * Number of bytes in @e data, 0 for 0-terminated.
+   * Number of bytes in @e data.
    */
   size_t data_size;
 
   /**
-   * Function to call to handle the request.
+   * Maximum upload size allowed for this handler.
+   * 0 for DEFAULT_MAX_UPLOAD_SIZE
+   */
+  size_t max_upload;
+
+  /**
+   * Handler to be called for this URL/METHOD combination.
    *
    * @param rh this struct
-   * @param mime_type the @e mime_type for the reply (hint, can be NULL)
    * @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
-   * @param mi merchant backend instance, never NULL
+   * @param[in,out] hc context with further information about the request
    * @return MHD result code
    */
-  MHD_RESULT (*handler)(struct TMH_RequestHandler *rh,
-                        struct MHD_Connection *connection,
-                        void **connection_cls,
-                        const char *upload_data,
-                        size_t *upload_data_size,
-                        struct MerchantInstance *mi);
+  MHD_RESULT
+  (*handler)(const struct TMH_RequestHandler *rh,
+             struct MHD_Connection *connection,
+             struct TMH_HandlerContext *hc);
 
   /**
-   * Default response code.
+   * Default response code to use.
    */
   unsigned int response_code;
 };
 
 
-/**
- * Each MHD response handler that sets the "connection_cls" to a
- * non-NULL value must use a struct that has this struct as its first
- * member.  This struct contains a single callback, which will be
- * invoked to clean up the memory when the contection is completed.
- */
-struct TM_HandlerContext;
-
 /**
  * Signature of a function used to clean up the context
  * we keep in the "connection_cls" of MHD when handling
  * a request.
  *
- * @param hc header of the context to clean up.
+ * @param ctxt the context to clean up.
  */
 typedef void
-(*TM_ContextCleanup)(struct TM_HandlerContext *hc);
+(*TMH_ContextCleanup)(void *ctx);
 
 
 /**
- * Each MHD response handler that sets the "connection_cls" to a
- * non-NULL value must use a struct that has this struct as its first
- * member.  This struct contains a single callback, which will be
- * invoked to clean up the memory when the connection is completed.
+ * This information is stored in the "connection_cls" of MHD for
+ * every request that we process.
+ * Individual handlers can evaluate tis memebers and
+ * are allowed to update @e cc and @e ctx to store and clean up
+ * handler-specific data.
  */
-struct TM_HandlerContext
+struct TMH_HandlerContext
 {
 
   /**
    * Function to execute the handler-specific cleanup of the
-   * (typically larger) context.
+   * (request-specific) context in @e ctx.
    */
-  TM_ContextCleanup cc;
+  TMH_ContextCleanup cc;
+
+  /**
+   * Client-specific context we keep. Passed to @e cc.
+   */
+  void *ctx;
 
   /**
    * Which request handler is handling this request?
    */
   const struct TMH_RequestHandler *rh;
 
+  /**
+   * Which instance is handling this request?
+   */
+  struct TMH_MerchantInstance *instance;
+
   /**
    * Asynchronous request context id.
    */
   struct GNUNET_AsyncScopeId async_scope_id;
+
+  /**
+   * Our original URL, for logging.
+   */
+  const char *url;
+
+  /**
+   * Infix part of @a url.
+   */
+  char *infix;
+
+  /**
+   * JSON body that was uploaded, NULL if @e has_body is false.
+   */
+  json_t *request_body;
+
+  /**
+   * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
+   * Used when we parse the POSTed data.
+   */
+  void *json_parse_context;
+
+  /**
+   * Total size of the upload so far.
+   */
+  uint64_t total_upload;
+
+  /**
+   * Set to true if this is an #MHD_HTTP_METHOD_POST or #MHD_HTTP_METHOD_PATCH 
request.
+   * (In principle #MHD_HTTP_METHOD_PUT may also belong, but we do not have 
PUTs
+   * in the API today, so we do not test for PUT.)
+   */
+  bool has_body;
 };
 
 
@@ -247,12 +332,14 @@ struct TMH_SuspendedConnection
   struct MHD_Connection *con;
 
   /**
-   * Associated heap node.
+   * Associated heap node.  Used internally by #TMH_long_poll_suspend()
+   * and TMH_long_poll_resume().
    */
   struct GNUNET_CONTAINER_HeapNode *hn;
 
   /**
-   * Key of this entry in the #payment_trigger_map
+   * Key of this entry in the #payment_trigger_map.  Used internally by
+   * #TMH_long_poll_suspend() and TMH_long_poll_resume().
    */
   struct GNUNET_HashCode key;
 
@@ -270,52 +357,11 @@ struct TMH_SuspendedConnection
   /**
    * #GNUNET_YES if we are waiting for a refund.
    */
-  int awaiting_refund;
+  bool awaiting_refund;
 
 };
 
 
-/**
- * Locations from the configuration.  Mapping from
- * label to location data.
- */
-extern json_t *default_locations;
-
-/**
- * Default maximum wire fee to assume, unless stated differently in the 
proposal
- * already.
- */
-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;
-
-/**
- * MIN-Heap of suspended connections to resume when the timeout expires,
- * ordered by timeout. Values are of type `struct TMH_SuspendedConnection`
- */
-extern struct GNUNET_CONTAINER_Heap *resume_timeout_heap;
-
-/**
- * Task responsible for timeouts in the #resume_timeout_heap.
- */
-extern struct GNUNET_SCHEDULER_Task *resume_timeout_task;
-
-/**
- * Hash map from H(order_id,merchant_pub) to `struct TMH_SuspendedConnection`
- * entries to resume when a payment is made for the given order.
- */
-extern struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map;
-
 /**
  * Which currency do we use?
  */
@@ -327,45 +373,24 @@ extern char *TMH_currency;
 extern int TMH_force_audit;
 
 /**
- * Hash of our wire format details as given in #j_wire.
- */
-extern struct GNUNET_HashCode h_wire;
-
-/**
- * Our private key, corresponds to #pubkey.
- */
-extern struct TALER_MerchantPrivateKeyP privkey;
-
-/**
- * Our public key, corresponds to #privkey.
+ * Handle to the database backend.
  */
-extern struct TALER_MerchantPublicKeyP pubkey;
+extern struct TALER_MERCHANTDB_Plugin *TMH_db;
 
 /**
  * Hashmap pointing at merchant instances by 'id'. An 'id' is
  * just a string that identifies a merchant instance. When a frontend
  * needs to specify an instance to the backend, it does so by 'id'
  */
-extern struct GNUNET_CONTAINER_MultiHashMap *by_id_map;
-
-/**
- * Handle to the database backend.
- */
-extern struct TALER_MERCHANTDB_Plugin *db;
+extern struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map;
 
 /**
- * If the frontend does NOT specify an execution date, how long should
- * we tell the exchange to wait to aggregate transactions before
- * executing the wire transfer?  This delay is added to the current
- * time when we generate the advisory execution time for the exchange.
+ * How long do we need to keep information on paid contracts on file for tax
+ * or other legal reasons?  Used to block deletions for younger transaction
+ * data.
  */
-extern struct GNUNET_TIME_Relative default_wire_transfer_delay;
+extern struct GNUNET_TIME_Relative TMH_legal_expiration;
 
-/**
- * If the frontend does NOT specify a payment deadline, how long should
- * offers we make be valid by default?
- */
-extern struct GNUNET_TIME_Relative default_pay_deadline;
 
 /**
  * Kick MHD to run now, to be called after MHD_resume_connection().
@@ -377,29 +402,19 @@ void
 TMH_trigger_daemon (void);
 
 
-/**
- * Compute @a key to use for @a order_id and @a mpub in our
- * #payment_trigger_map.
- *
- * @param order_id an order ID
- * @param mpub an instance public key
- * @param key[out] set to the hash map key to use
- */
-void
-TMH_compute_pay_key (const char *order_id,
-                     const struct TALER_MerchantPublicKeyP *mpub,
-                     struct GNUNET_HashCode *key);
-
-
 /**
  * Suspend connection from @a sc until payment has been received.
  *
+ * @param order_id the order that we are waiting on
+ * @param mi the merchant instance we are waiting on
  * @param sc connection to suspend
  * @param min_refund refund amount we are waiting on to be exceeded before 
resuming,
  *                   NULL if we are not waiting for refunds
  */
 void
-TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc,
+TMH_long_poll_suspend (const char *order_id,
+                       const struct TMH_MerchantInstance *mi,
+                       struct TMH_SuspendedConnection *sc,
                        const struct TALER_Amount *min_refund);
 
 
@@ -407,31 +422,42 @@ TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc,
  * Find out if we have any clients long-polling for @a order_id to be
  * confirmed at merchant @a mpub, and if so, tell them to resume.
  *
- * @param order_id the order that was paid
- * @param mpub the merchant's public key of the instance where the payment 
happened
+ * @param order_id the order that was paid or refunded
+ * @param mi the merchant instance where the payment or refund happened
  * @param refund_amount refunded amount, if the trigger was a refund, 
otherwise NULL
  */
 void
 TMH_long_poll_resume (const char *order_id,
-                      const struct TALER_MerchantPublicKeyP *mpub,
+                      const struct TMH_MerchantInstance *mi,
                       const struct TALER_Amount *refund_amount);
 
 
 /**
- * Create a taler://pay/ URI for the given @a con and @a order_id
- * and @a session_id and @a instance_id.
+ * Decrement reference counter of @a mi, and free if it hits zero.
+ *
+ * @param[in,out] mi merchant instance to update and possibly free
+ */
+void
+TMH_instance_decref (struct TMH_MerchantInstance *mi);
+
+
+/**
+ * Add instance definition to our active set of instances.
+ *
+ * @param[in,out] mi merchant instance details to define
+ * @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already
+ */
+int
+TMH_add_instance (struct TMH_MerchantInstance *mi);
+
+/**
+ * Lookup a merchant instance by its instance ID.
  *
- * @param con HTTP connection
- * @param order_id the order id
- * @param session_id session, may be NULL
- * @param instance_id instance, may be "default"
- * @return corresponding taler://pay/ URI, or NULL on missing "host"
+ * @param instance_id identifier of the instance to resolve
+ * @return NULL if that instance is unknown to us
  */
-char *
-TMH_make_taler_pay_uri (struct MHD_Connection *con,
-                        const char *order_id,
-                        const char *session_id,
-                        const char *instance_id);
+struct TMH_MerchantInstance *
+TMH_lookup_instance (const char *instance_id);
 
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_refund.c 
b/src/backend/taler-merchant-httpd_OBSOLETE.c
similarity index 100%
rename from src/backend/taler-merchant-httpd_refund.c
rename to src/backend/taler-merchant-httpd_OBSOLETE.c
diff --git a/src/backend/taler-merchant-httpd_auditors.c 
b/src/backend/taler-merchant-httpd_auditors.c
index 225fa15..9237f92 100644
--- a/src/backend/taler-merchant-httpd_auditors.c
+++ b/src/backend/taler-merchant-httpd_auditors.c
@@ -69,7 +69,7 @@ json_t *j_auditors;
  *
  * @param mh exchange issuing @a dk
  * @param dk a denomination issued by @a mh
- * @param exchange_trusted #GNUNET_YES if the exchange of @a dk is trusted by 
config
+ * @param exchange_trusted true if the exchange of @a dk is trusted by config
  * @param[out] hc HTTP status code to return (on error)
  * @param[out] ec Taler error code to return (on error)
  * @return #GNUNET_OK
@@ -77,7 +77,7 @@ json_t *j_auditors;
 int
 TMH_AUDITORS_check_dk (struct TALER_EXCHANGE_Handle *mh,
                        const struct TALER_EXCHANGE_DenomPublicKey *dk,
-                       int exchange_trusted,
+                       bool exchange_trusted,
                        unsigned int *hc,
                        enum TALER_ErrorCode *ec)
 {
@@ -92,7 +92,7 @@ TMH_AUDITORS_check_dk (struct TALER_EXCHANGE_Handle *mh,
     *ec = TALER_EC_PAY_DENOMINATION_DEPOSIT_EXPIRED;
     return GNUNET_SYSERR; /* expired */
   }
-  if (GNUNET_YES == exchange_trusted)
+  if (exchange_trusted)
   {
     *ec = TALER_EC_NONE;
     *hc = MHD_HTTP_OK;
@@ -273,7 +273,7 @@ TMH_AUDITORS_done ()
     GNUNET_free (auditors[i].name);
     GNUNET_free (auditors[i].url);
   }
-  GNUNET_free_non_null (auditors);
+  GNUNET_free (auditors);
   auditors = NULL;
   nauditors = 0;
 }
diff --git a/src/backend/taler-merchant-httpd_auditors.h 
b/src/backend/taler-merchant-httpd_auditors.h
index 4a1a0b6..4786b80 100644
--- a/src/backend/taler-merchant-httpd_auditors.h
+++ b/src/backend/taler-merchant-httpd_auditors.h
@@ -53,7 +53,7 @@ TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle 
*cfg);
  *
  * @param mh exchange issuing @a dk
  * @param dk a denomination issued by @a mh
- * @param exchange_trusted #GNUNET_YES if the exchange of @a dk is trusted by 
config
+ * @param exchange_trusted true if the exchange of @a dk is trusted by config
  * @param[out] hc set to the HTTP status code to return
  * @param[out] ec set to the Taler error code to return
  * @return #GNUNET_OK on success
@@ -61,7 +61,7 @@ TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle 
*cfg);
 int
 TMH_AUDITORS_check_dk (struct TALER_EXCHANGE_Handle *mh,
                        const struct TALER_EXCHANGE_DenomPublicKey *dk,
-                       int exchange_trusted,
+                       bool exchange_trusted,
                        unsigned int *hc,
                        enum TALER_ErrorCode *ec);
 
diff --git a/src/backend/taler-merchant-httpd_check-payment.c 
b/src/backend/taler-merchant-httpd_check-payment.c
deleted file mode 100644
index bb5384d..0000000
--- a/src/backend/taler-merchant-httpd_check-payment.c
+++ /dev/null
@@ -1,591 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2017, 2019 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_check-payment.c
- * @brief implementation of /check-payment handler
- * @author Florian Dold
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <string.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_exchanges.h"
-#include "taler-merchant-httpd_check-payment.h"
-
-/**
- * Maximum number of retries for database operations.
- */
-#define MAX_RETRIES 5
-
-
-/**
- * Data structure we keep for a check payment request.
- */
-struct CheckPaymentRequestContext
-{
-  /**
-   * Must be first for #handle_mhd_completion_callback.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Entry in the #resume_timeout_heap for this check payment, if we are
-   * suspended.
-   */
-  struct TMH_SuspendedConnection sc;
-
-  /**
-   * Which merchant instance is this for?
-   */
-  struct MerchantInstance *mi;
-
-  /**
-   * URL where the final contract can be found for this payment.
-   */
-  char *final_contract_url;
-
-  /**
-   * order ID for the payment
-   */
-  const char *order_id;
-
-  /**
-   * Where to get the contract
-   */
-  const char *contract_url;
-
-  /**
-   * session of the client
-   */
-  const char *session_id;
-
-  /**
-   * fulfillment URL of the contract (valid as long as
-   * @e contract_terms is valid).
-   */
-  const char *fulfillment_url;
-
-  /**
-   * Contract terms of the payment we are checking. NULL when they
-   * are not (yet) known.
-   */
-  json_t *contract_terms;
-
-  /**
-   * Hash of @e contract_terms, set only once @e contract_terms
-   * is available.
-   */
-  struct GNUNET_HashCode h_contract_terms;
-
-  /**
-   * Total refunds granted for this payment. Only initialized
-   * if @e refunded is set to #GNUNET_YES.
-   */
-  struct TALER_Amount refund_amount;
-
-  /**
-   * Set to #GNUNET_YES if this payment has been refunded and
-   * @e refund_amount is initialized.
-   */
-  int refunded;
-
-  /**
-   * Initially #GNUNET_SYSERR. If we queued a response, set to the
-   * result code (i.e. #MHD_YES or #MHD_NO).
-   */
-  int ret;
-
-};
-
-
-/**
- * Clean up the session state for a check payment request.
- *
- * @param hc must be a `struct CheckPaymentRequestContext *`
- */
-static void
-cprc_cleanup (struct TM_HandlerContext *hc)
-{
-  struct CheckPaymentRequestContext *cprc = (struct
-                                             CheckPaymentRequestContext *) hc;
-
-  if (NULL != cprc->contract_terms)
-    json_decref (cprc->contract_terms);
-  GNUNET_free_non_null (cprc->final_contract_url);
-  GNUNET_free (cprc);
-}
-
-
-/**
- * Function called with information about a refund.
- * It is responsible for summing up the refund amount.
- *
- * @param cls closure
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
- */
-static void
-process_refunds_cb (void *cls,
-                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                    const char *exchange_url,
-                    uint64_t rtransaction_id,
-                    const char *reason,
-                    const struct TALER_Amount *refund_amount,
-                    const struct TALER_Amount *refund_fee)
-{
-  struct CheckPaymentRequestContext *cprc = cls;
-
-  if (cprc->refunded)
-  {
-    GNUNET_assert (0 <=
-                   TALER_amount_add (&cprc->refund_amount,
-                                     &cprc->refund_amount,
-                                     refund_amount));
-    return;
-  }
-  cprc->refund_amount = *refund_amount;
-  cprc->refunded = GNUNET_YES;
-}
-
-
-/**
- * The client did not yet pay, send it the payment request.
- *
- * @param cprc check pay request context
- * @return #MHD_YES on success
- */
-static int
-send_pay_request (struct CheckPaymentRequestContext *cprc)
-{
-  int ret;
-  char *already_paid_order_id = NULL;
-  char *taler_pay_uri;
-  struct GNUNET_TIME_Relative remaining;
-
-  remaining = GNUNET_TIME_absolute_get_remaining (cprc->sc.long_poll_timeout);
-  if (0 != remaining.rel_value_us)
-  {
-    /* long polling: do not queue a response, suspend connection instead */
-    TMH_compute_pay_key (cprc->order_id,
-                         &cprc->mi->pubkey,
-                         &cprc->sc.key);
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Suspending /check-payment on key %s\n",
-                GNUNET_h2s (&cprc->sc.key));
-    TMH_long_poll_suspend (&cprc->sc,
-                           NULL);
-    return MHD_YES;
-  }
-
-  /* Check if resource_id has been paid for in the same session
-   * with another order_id.
-   */
-  if ( (NULL != cprc->session_id) &&
-       (NULL != cprc->fulfillment_url) )
-  {
-    enum GNUNET_DB_QueryStatus qs;
-
-    qs = db->find_session_info (db->cls,
-                                &already_paid_order_id,
-                                cprc->session_id,
-                                cprc->fulfillment_url,
-                                &cprc->mi->pubkey);
-    if (qs < 0)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (cprc->sc.con,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
-                                         "db error fetching pay session info");
-    }
-  }
-  taler_pay_uri = TMH_make_taler_pay_uri (cprc->sc.con,
-                                          cprc->order_id,
-                                          cprc->session_id,
-                                          cprc->mi->id);
-  ret = TALER_MHD_reply_json_pack (cprc->sc.con,
-                                   MHD_HTTP_OK,
-                                   "{s:s, s:s, s:b, s:s?}",
-                                   "taler_pay_uri", taler_pay_uri,
-                                   "contract_url", cprc->final_contract_url,
-                                   "paid", 0,
-                                   "already_paid_order_id",
-                                   already_paid_order_id);
-  GNUNET_free (taler_pay_uri);
-  GNUNET_free_non_null (already_paid_order_id);
-  return ret;
-}
-
-
-/**
- * Parse the "contract_terms" in @a cprc and set the
- * "fulfillment_url" and the "h_contract_terms" in @a cprc
- * accordingly.
- *
- * On errors, the response is being queued and the status
- * code set in @cprc "ret".
- *
- * @param cprc[in,out] context to process
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
- */
-static int
-parse_contract_terms (struct CheckPaymentRequestContext *cprc)
-{
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_string ("fulfillment_url",
-                             &cprc->fulfillment_url),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (cprc->contract_terms,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break (0);
-    cprc->ret
-      = TALER_MHD_reply_with_error (cprc->sc.con,
-                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                    
TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR,
-                                    "Merchant database error (contract terms 
corrupted)");
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_JSON_hash (cprc->contract_terms,
-                       &cprc->h_contract_terms))
-  {
-    GNUNET_break (0);
-    cprc->ret
-      = TALER_MHD_reply_with_error (cprc->sc.con,
-                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                    
TALER_EC_CHECK_PAYMENT_FAILED_COMPUTE_PROPOSAL_HASH,
-                                    "Failed to hash proposal");
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Check that we are aware of @a order_id and if so request the payment,
- * otherwise generate an error response.
- *
- * @param cprc session state
- * @return status code to return to MHD for @a connection
- */
-static int
-check_order_and_request_payment (struct CheckPaymentRequestContext *cprc)
-{
-  enum GNUNET_DB_QueryStatus qs;
-
-  if (NULL != cprc->contract_terms)
-  {
-    /* This should never happen. */
-    GNUNET_break (0);
-    json_decref (cprc->contract_terms);
-    cprc->contract_terms = NULL;
-  }
-  qs = db->find_order (db->cls,
-                       &cprc->contract_terms,
-                       cprc->order_id,
-                       &cprc->mi->pubkey);
-  if (0 > qs)
-  {
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TALER_MHD_reply_with_error (cprc->sc.con,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
-                                       "db error fetching order");
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    return TALER_MHD_reply_with_error (cprc->sc.con,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_CHECK_PAYMENT_ORDER_ID_UNKNOWN,
-                                       "unknown order_id");
-  }
-
-  if (GNUNET_OK !=
-      parse_contract_terms (cprc))
-    return cprc->ret;
-  /* Offer was not picked up yet, but we ensured that it exists */
-  return send_pay_request (cprc);
-}
-
-
-/**
- * Manages a /check-payment call, checking the status
- * of a payment and, if necessary, constructing the URL
- * for a payment redirect URL.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_check_payment (struct TMH_RequestHandler *rh,
-                          struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi)
-{
-  struct CheckPaymentRequestContext *cprc = *connection_cls;
-  enum GNUNET_DB_QueryStatus qs;
-  MHD_RESULT ret;
-
-  if (NULL == cprc)
-  {
-    const char *long_poll_timeout_s;
-
-    /* First time here, parse request and check order is known */
-    cprc = GNUNET_new (struct CheckPaymentRequestContext);
-    cprc->hc.cc = &cprc_cleanup;
-    cprc->ret = GNUNET_SYSERR;
-    cprc->sc.con = connection;
-    cprc->mi = mi;
-    *connection_cls = cprc;
-
-    cprc->order_id = MHD_lookup_connection_value (connection,
-                                                  MHD_GET_ARGUMENT_KIND,
-                                                  "order_id");
-    if (NULL == cprc->order_id)
-    {
-      /* order_id is required but missing */
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "order_id required");
-    }
-    cprc->contract_url = MHD_lookup_connection_value (connection,
-                                                      MHD_GET_ARGUMENT_KIND,
-                                                      "contract_url");
-    if (NULL == cprc->contract_url)
-    {
-      cprc->final_contract_url = TALER_url_absolute_mhd (connection,
-                                                         "/public/proposal",
-                                                         "instance", mi->id,
-                                                         "order_id",
-                                                         cprc->order_id,
-                                                         NULL);
-      GNUNET_assert (NULL != cprc->final_contract_url);
-    }
-    else
-    {
-      cprc->final_contract_url = GNUNET_strdup (cprc->contract_url);
-    }
-    cprc->session_id = MHD_lookup_connection_value (connection,
-                                                    MHD_GET_ARGUMENT_KIND,
-                                                    "session_id");
-    long_poll_timeout_s = MHD_lookup_connection_value (connection,
-                                                       MHD_GET_ARGUMENT_KIND,
-                                                       "timeout");
-    if (NULL != long_poll_timeout_s)
-    {
-      unsigned int timeout;
-
-      if (1 != sscanf (long_poll_timeout_s,
-                       "%u",
-                       &timeout))
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           TALER_EC_PARAMETER_MALFORMED,
-                                           "timeout must be non-negative 
number");
-      }
-      cprc->sc.long_poll_timeout
-        = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
-                                              GNUNET_TIME_UNIT_SECONDS,
-                                              timeout));
-    }
-    else
-    {
-      cprc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Starting /check-payment processing with timeout %s\n",
-                GNUNET_STRINGS_absolute_time_to_string (
-                  cprc->sc.long_poll_timeout));
-  }
-  if (NULL != cprc->contract_terms)
-  {
-    json_decref (cprc->contract_terms);
-    cprc->contract_terms = NULL;
-  }
-  db->preflight (db->cls);
-  qs = db->find_contract_terms (db->cls,
-                                &cprc->contract_terms,
-                                cprc->order_id,
-                                &mi->pubkey);
-  if (0 > qs)
-  {
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR,
-                                       "db error fetching contract terms");
-  }
-
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    /* Check that we're at least aware of the order */
-    return check_order_and_request_payment (cprc);
-  }
-  GNUNET_assert (NULL != cprc->contract_terms);
-
-  if (GNUNET_OK !=
-      parse_contract_terms (cprc))
-    return cprc->ret;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Checkig payment status for order `%s' with contract %s\n",
-              cprc->order_id,
-              GNUNET_h2s (&cprc->h_contract_terms));
-  GNUNET_assert (NULL != cprc->contract_terms);
-
-  /* Check if the order has been paid for. */
-  if (NULL != cprc->session_id)
-  {
-    /* Check if paid within a session. */
-    char *already_paid_order_id = NULL;
-
-    qs = db->find_session_info (db->cls,
-                                &already_paid_order_id,
-                                cprc->session_id,
-                                cprc->fulfillment_url,
-                                &mi->pubkey);
-    if (qs < 0)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
-                                         "db error fetching pay session info");
-    }
-    else if (0 == qs)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Order `%s' was not yet paid for session `%s'\n",
-                  cprc->order_id,
-                  cprc->session_id);
-      ret = send_pay_request (cprc);
-      GNUNET_free_non_null (already_paid_order_id);
-      return ret;
-    }
-    GNUNET_break (1 == qs);
-    GNUNET_break (0 == strcmp (cprc->order_id,
-                               already_paid_order_id));
-    GNUNET_free_non_null (already_paid_order_id);
-  }
-  else
-  {
-    /* Check if paid regardless of session. */
-    json_t *xcontract_terms = NULL;
-
-    qs = db->find_paid_contract_terms_from_hash (db->cls,
-                                                 &xcontract_terms,
-                                                 &cprc->h_contract_terms,
-                                                 &mi->pubkey);
-    if (0 > qs)
-    {
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                         "Merchant database error");
-    }
-    if (0 == qs)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Order `%s' (contract `%s') was not yet paid\n",
-                  cprc->order_id,
-                  GNUNET_h2s (&cprc->h_contract_terms));
-      return send_pay_request (cprc);
-    }
-    GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
-    GNUNET_assert (NULL != xcontract_terms);
-    json_decref (xcontract_terms);
-  }
-
-  /* Accumulate refunds, if any. */
-  for (unsigned int i = 0; i<MAX_RETRIES; i++)
-  {
-    qs = db->get_refunds_from_contract_terms_hash (db->cls,
-                                                   &mi->pubkey,
-                                                   &cprc->h_contract_terms,
-                                                   &process_refunds_cb,
-                                                   cprc);
-    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-      break;
-  }
-  if (0 > qs)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Database hard error on refunds_from_contract_terms_hash 
lookup: %s\n",
-                GNUNET_h2s (&cprc->h_contract_terms));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                       "Merchant database error");
-  }
-  if (cprc->refunded)
-    return TALER_MHD_reply_json_pack (connection,
-                                      MHD_HTTP_OK,
-                                      "{s:O, s:b, s:b, s:o}",
-                                      "contract_terms", cprc->contract_terms,
-                                      "paid", 1,
-                                      "refunded", cprc->refunded,
-                                      "refund_amount",
-                                      TALER_JSON_from_amount (
-                                        &cprc->refund_amount));
-  return TALER_MHD_reply_json_pack (connection,
-                                    MHD_HTTP_OK,
-                                    "{s:O, s:b, s:b }",
-                                    "contract_terms", cprc->contract_terms,
-                                    "paid", 1,
-                                    "refunded", 0);
-}
diff --git a/src/backend/taler-merchant-httpd_config.c 
b/src/backend/taler-merchant-httpd_config.c
index d6f6c92..c4fed58 100644
--- a/src/backend/taler-merchant-httpd_config.c
+++ b/src/backend/taler-merchant-httpd_config.c
@@ -25,8 +25,6 @@
 #include "taler-merchant-httpd.h"
 #include "taler-merchant-httpd_mhd.h"
 #include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_tip-query.h"
-#include "taler-merchant-httpd_tip-reserve-helper.h"
 
 
 /**
@@ -47,111 +45,28 @@
 #define MERCHANT_PROTOCOL_VERSION "0:0:0"
 
 
-static int
-add_instance (void *cls,
-              const struct GNUNET_HashCode *key,
-              void *value)
-{
-  json_t *ja = cls;
-  struct MerchantInstance *mi = value;
-  char *url;
-  json_t *pta;
-
-  /* Compile array of all unique wire methods supported by this
-     instance */
-  pta = json_array ();
-  GNUNET_assert (NULL != pta);
-  for (struct WireMethod *wm = mi->wm_head;
-       NULL != wm;
-       wm = wm->next)
-  {
-    int duplicate = GNUNET_NO;
-
-    if (! wm->active)
-      break;
-    /* Yes, O(n^2), but really how many bank accounts can an
-       instance realistically have for this to matter? */
-    for (struct WireMethod *pm = mi->wm_head;
-         pm != wm;
-         pm = pm->next)
-      if (0 == strcasecmp (pm->wire_method,
-                           wm->wire_method))
-      {
-        duplicate = GNUNET_YES;
-        break;
-      }
-    if (duplicate)
-      continue;
-    GNUNET_assert (0 ==
-                   json_array_append_new (pta,
-                                          json_string (wm->wire_method)));
-  }
-  GNUNET_asprintf (&url,
-                   "/%s/",
-                   mi->id);
-  GNUNET_assert (0 ==
-                 json_array_append_new (
-                   ja,
-                   json_pack (
-                     (NULL != mi->tip_exchange)
-                     ? "{s:s, s:s, s:o, s:o, s:s}"
-                     : "{s:s, s:s, s:o, s:o}",
-                     "name",
-                     mi->name,
-                     "backend_base_url",
-                     url,
-                     "merchant_pub",
-                     GNUNET_JSON_from_data_auto (&mi->pubkey),
-                     "payment_targets",
-                     pta,
-                     /* optional: */
-                     "tipping_exchange_baseurl",
-                     mi->tip_exchange)));
-  GNUNET_free (url);
-  return GNUNET_OK;
-}
-
-
 /**
  * Handle a "/config" 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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc handler context (can be updated)
  * @return MHD result code
  */
 MHD_RESULT
 MH_handler_config (struct TMH_RequestHandler *rh,
                    struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size,
-                   struct MerchantInstance *mi)
+                   struct TMH_HandlerContext *hc)
 {
   static struct MHD_Response *response;
 
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  (void) mi;
+  (void) hc;
   if (NULL == response)
   {
-    json_t *ia;
-
-    ia = json_array ();
-    GNUNET_assert (NULL != ia);
-    GNUNET_CONTAINER_multihashmap_iterate (by_id_map,
-                                           &add_instance,
-                                           ia);
-    response = TALER_MHD_make_json_pack ("{s:s, s:s, s:o}",
+    response = TALER_MHD_make_json_pack ("{s:s, s:s }",
                                          "currency", TMH_currency,
-                                         "version", MERCHANT_PROTOCOL_VERSION,
-                                         "instances", ia);
-
+                                         "version", MERCHANT_PROTOCOL_VERSION);
   }
   return MHD_queue_response (connection,
                              MHD_HTTP_OK,
diff --git a/src/backend/taler-merchant-httpd_config.h 
b/src/backend/taler-merchant-httpd_config.h
index 1a5dfd6..e6a713c 100644
--- a/src/backend/taler-merchant-httpd_config.h
+++ b/src/backend/taler-merchant-httpd_config.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2019 Taler Systems SA
+  (C) 2019, 2020 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
@@ -28,18 +28,12 @@
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc handler context (can be updated)
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_config (struct TMH_RequestHandler *rh,
+MH_handler_config (const struct TMH_RequestHandler *rh,
                    struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size,
-                   struct MerchantInstance *mi);
+                   struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_exchanges.c 
b/src/backend/taler-merchant-httpd_exchanges.c
index 190f5db..46d241a 100644
--- a/src/backend/taler-merchant-httpd_exchanges.c
+++ b/src/backend/taler-merchant-httpd_exchanges.c
@@ -22,6 +22,7 @@
 #include "platform.h"
 #include <taler/taler_json_lib.h>
 #include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd.h"
 
 
 /**
@@ -128,6 +129,11 @@ struct FeesByWireMethod
    */
   char *wire_method;
 
+  /**
+   * Full payto URI of the exchange.
+   */
+  char *payto_uri;
+
   /**
    * Applicable fees, NULL if unknown/error.
    */
@@ -219,18 +225,18 @@ struct Exchange
   struct GNUNET_SCHEDULER_Task *retry_task;
 
   /**
-   * #GNUNET_YES to indicate that there is an ongoing
+   * true to indicate that there is an ongoing
    * transfer we are waiting for,
-   * #GNUNET_NO to indicate that key data is up-to-date.
+   * false to indicate that key data is up-to-date.
    */
-  int pending;
+  bool pending;
 
   /**
-   * #GNUNET_YES if this exchange is from our configuration and
-   * explicitly trusted, #GNUNET_NO if we need to check each
+   * true if this exchange is from our configuration and
+   * explicitly trusted, false if we need to check each
    * key to be sure it is trusted.
    */
-  int trusted;
+  bool trusted;
 
 };
 
@@ -321,10 +327,12 @@ retry_exchange (void *cls)
 /**
  * Function called with information about the wire fees
  * for each wire method.  Stores the wire fees with the
- * exchange for laster use.
+ * exchange for later use.
  *
  * @param cls closure
- * @param wire_method name of the wire method (i.e. "sepa")
+ * @param master_pub public key of the exchange
+ * @param wire_method name of the wire method (i.e. "iban")
+ * @param payto_uri full payto URI of the exchange
  * @param fees fee structure for this method
  * @return #TALER_EC_NONE on success
  */
@@ -332,6 +340,7 @@ static enum TALER_ErrorCode
 process_wire_fees (struct Exchange *exchange,
                    const struct TALER_MasterPublicKeyP *master_pub,
                    const char *wire_method,
+                   const char *payto_uri,
                    const struct TALER_EXCHANGE_WireAggregateFees *fees)
 {
   struct FeesByWireMethod *f;
@@ -346,6 +355,7 @@ process_wire_fees (struct Exchange *exchange,
   {
     f = GNUNET_new (struct FeesByWireMethod);
     f->wire_method = GNUNET_strdup (wire_method);
+    f->payto_uri = GNUNET_strdup (payto_uri);
     GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
                                  exchange->wire_fees_tail,
                                  f);
@@ -380,10 +390,10 @@ process_wire_fees (struct Exchange *exchange,
                 wire_method,
                 GNUNET_STRINGS_absolute_time_to_string (af->start_date),
                 TALER_amount2s (&af->wire_fee));
-    db->preflight (db->cls);
+    TMH_db->preflight (TMH_db->cls);
     if (GNUNET_OK !=
-        db->start (db->cls,
-                   "store wire fee"))
+        TMH_db->start (TMH_db->cls,
+                       "store wire fee"))
     {
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                   "Failed to start database transaction to store exchange wire 
fees (will try to continue anyway)!\n");
@@ -391,21 +401,21 @@ process_wire_fees (struct Exchange *exchange,
       fees = fees->next;
       continue;
     }
-    qs = db->store_wire_fee_by_exchange (db->cls,
-                                         master_pub,
-                                         &h_wire_method,
-                                         &af->wire_fee,
-                                         &af->closing_fee,
-                                         af->start_date,
-                                         af->end_date,
-                                         &af->master_sig);
+    qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls,
+                                             master_pub,
+                                             &h_wire_method,
+                                             &af->wire_fee,
+                                             &af->closing_fee,
+                                             af->start_date,
+                                             af->end_date,
+                                             &af->master_sig);
     if (0 > qs)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                   "Failed to persist exchange wire fees in merchant DB (will 
try to continue anyway)!\n");
       GNUNET_free (af);
       fees = fees->next;
-      db->rollback (db->cls);
+      TMH_db->rollback (TMH_db->cls);
       continue;
     }
     if (0 == qs)
@@ -413,12 +423,12 @@ process_wire_fees (struct Exchange *exchange,
       /* Entry was already in DB, fine, continue as if we had succeeded */
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                   "Fees already in DB, rolling back transaction attempt!\n");
-      db->rollback (db->cls);
+      TMH_db->rollback (TMH_db->cls);
     }
     if (0 < qs)
     {
       /* Inserted into DB, make sure transaction completes */
-      qs = db->commit (db->cls);
+      qs = TMH_db->commit (TMH_db->cls);
       if (0 > qs)
       {
         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -442,7 +452,7 @@ process_wire_fees (struct Exchange *exchange,
 
 /**
  * Function called with information about the wire accounts
- * of the exchanage.  Stores the wire fees with the
+ * of the exchange.  Stores the wire fees with the
  * exchange for laster use.
  *
  * @param exchange the exchange
@@ -472,6 +482,7 @@ process_wire_accounts (struct Exchange *exchange,
     ec = process_wire_fees (exchange,
                             master_pub,
                             method,
+                            accounts[i].payto_uri,
                             accounts[i].fees);
     GNUNET_free (method);
     if (TALER_EC_NONE != ec)
@@ -489,7 +500,7 @@ process_wire_accounts (struct Exchange *exchange,
  * @param wire_method the wire method we want the fees for
  * @return NULL if we do not have fees for this method yet
  */
-static struct TALER_EXCHANGE_WireAggregateFees *
+static struct FeesByWireMethod *
 get_wire_fees (struct Exchange *exchange,
                struct GNUNET_TIME_Absolute now,
                const char *wire_method)
@@ -497,6 +508,7 @@ get_wire_fees (struct Exchange *exchange,
   for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
        NULL != fbw;
        fbw = fbw->next)
+  {
     if (0 == strcasecmp (fbw->wire_method,
                          wire_method) )
     {
@@ -509,8 +521,12 @@ get_wire_fees (struct Exchange *exchange,
         fbw->af = af->next;
         GNUNET_free (af);
       }
-      return af;
+      return fbw;
     }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Exchange supports `%s' as a wire method (but we do not use 
that one)\n",
+                fbw->wire_method);
+  }
   return NULL;
 }
 
@@ -521,54 +537,55 @@ get_wire_fees (struct Exchange *exchange,
  * the callback.
  *
  * @param exchange the exchange to check for pending find operations
- * @return #GNUNET_YES if we need /wire data from @a exchange
+ * @return true if we need /wire data from @a exchange
  */
-static int
+static bool
 process_find_operations (struct Exchange *exchange)
 {
   struct TMH_EXCHANGES_FindOperation *fn;
   struct GNUNET_TIME_Absolute now;
-  int need_wire;
+  bool need_wire;
 
   now = GNUNET_TIME_absolute_get ();
-  need_wire = GNUNET_NO;
+  need_wire = false;
   for (struct TMH_EXCHANGES_FindOperation *fo = exchange->fo_head;
        NULL != fo;
        fo = fn)
   {
-    const struct TALER_Amount *wire_fee;
+    struct FeesByWireMethod *fbw;
 
     fn = fo->next;
     if (NULL != fo->wire_method)
     {
-      struct TALER_EXCHANGE_WireAggregateFees *af;
-
       /* Find fee structure for our wire method */
-      af = get_wire_fees (exchange,
-                          now,
-                          fo->wire_method);
-      if (NULL == af)
+      fbw = get_wire_fees (exchange,
+                           now,
+                           fo->wire_method);
+      if (NULL == fbw)
       {
-        need_wire = GNUNET_YES;
+        need_wire = true;
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Exchange does not support `%s' wire method (will retry 
later)\n",
+                    fo->wire_method);
+        fbw = NULL;
         continue;
       }
-      if (af->start_date.abs_value_us > now.abs_value_us)
+      if (fbw->af->start_date.abs_value_us > now.abs_value_us)
       {
         /* Disagreement on the current time */
         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                     "Exchange's earliest fee is %s adhead of our time. Clock 
skew issue?\n",
                     GNUNET_STRINGS_relative_time_to_string (
-                      GNUNET_TIME_absolute_get_remaining (af->start_date),
+                      GNUNET_TIME_absolute_get_remaining (fbw->af->start_date),
                       GNUNET_YES));
+        fbw = NULL;
         continue;
       }
-      /* found fee, great! */
-      wire_fee = &af->wire_fee;
     }
     else
     {
       /* no wire transfer method given, so we yield no fee */
-      wire_fee = NULL;
+      fbw = NULL;
     }
     {
       struct TALER_EXCHANGE_HttpResponse hr = {
@@ -578,7 +595,8 @@ process_find_operations (struct Exchange *exchange)
       fo->fc (fo->fc_cls,
               &hr,
               exchange->conn,
-              wire_fee,
+              (NULL != fbw) ? fbw->payto_uri : NULL,
+              (NULL != fbw) ? &fbw->af->wire_fee : NULL,
               exchange->trusted);
     }
     TMH_EXCHANGES_find_exchange_cancel (fo);
@@ -638,6 +656,7 @@ handle_wire_data (void *cls,
     {
       fo->fc (fo->fc_cls,
               hr,
+              exchange->conn,
               NULL,
               NULL,
               GNUNET_NO);
@@ -668,13 +687,13 @@ handle_wire_data (void *cls,
               &hrx,
               NULL,
               NULL,
+              NULL,
               GNUNET_NO);
       TMH_EXCHANGES_find_exchange_cancel (fo);
     }
     return;
   }
-  if ( (GNUNET_YES ==
-        process_find_operations (exchange)) &&
+  if ( (process_find_operations (exchange)) &&
        (NULL == exchange->wire_task) &&
        (NULL == exchange->wire_request) )
   {
@@ -687,6 +706,7 @@ handle_wire_data (void *cls,
     exchange->wire_retry_delay = RETRY_BACKOFF (exchange->wire_retry_delay);
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Exchange does not support our wire method. Retrying in %s\n",
+
                 GNUNET_STRINGS_relative_time_to_string (
                   exchange->wire_retry_delay,
                   GNUNET_YES));
@@ -715,9 +735,8 @@ wire_task_cb (void *cls)
   struct Exchange *exchange = cls;
 
   exchange->wire_task = NULL;
-  GNUNET_assert (GNUNET_NO == exchange->pending);
-  if (GNUNET_YES !=
-      process_find_operations (exchange))
+  GNUNET_assert (! exchange->pending);
+  if (! process_find_operations (exchange))
     return; /* no more need */
   GNUNET_assert (NULL == exchange->wire_request);
   exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
@@ -726,6 +745,71 @@ wire_task_cb (void *cls)
 }
 
 
+/**
+ * We failed downloading /keys from @a exchange. Tell clients
+ * about our failure, abort pending operations and retry later.
+ *
+ * @param exchange exchange that failed
+ * @param hr details about the HTTP reply
+ * @param compat version compatibility data
+ */
+static void
+fail_and_retry (struct Exchange *exchange,
+                const struct TALER_EXCHANGE_HttpResponse *hr,
+                enum TALER_EXCHANGE_VersionCompatibility compat)
+{
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  exchange->pending = true;
+  if (NULL != exchange->wire_request)
+  {
+    TALER_EXCHANGE_wire_cancel (exchange->wire_request);
+    exchange->wire_request = NULL;
+  }
+  if (NULL != exchange->wire_task)
+  {
+    GNUNET_SCHEDULER_cancel (exchange->wire_task);
+    exchange->wire_task = NULL;
+  }
+  while (NULL != (fo = exchange->fo_head))
+  {
+    fo->fc (fo->fc_cls,
+            hr,
+            NULL,
+            NULL,
+            NULL,
+            GNUNET_NO);
+    TMH_EXCHANGES_find_exchange_cancel (fo);
+  }
+  if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat)
+  {
+    /* Log hard error: we likely need admin help! */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Exchange `%s' runs an incompatible more recent version of the 
Taler protocol. Will not retry. This client may need to be updated.\n",
+                exchange->url);
+    /* Theoretically, the exchange could downgrade,
+       but let's not be too aggressive about retries
+       on this one. */
+    exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS,
+                                                      exchange->retry_delay);
+  }
+  exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "Failed to fetch /keys from `%s': %d/%u, retrying in %s\n",
+              exchange->url,
+              (int) hr->ec,
+              hr->http_status,
+              GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay,
+                                                      GNUNET_YES));
+  GNUNET_assert (NULL == exchange->retry_task);
+  exchange->first_retry = GNUNET_TIME_relative_to_absolute (
+    exchange->retry_delay);
+  exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
+                                                       &retry_exchange,
+                                                       exchange);
+}
+
+
 /**
  * Function called with information about who is auditing
  * a particular exchange and what key the exchange is using.
@@ -754,57 +838,12 @@ keys_mgmt_cb (void *cls,
 
   if (NULL == keys)
   {
-    struct TMH_EXCHANGES_FindOperation *fo;
-
-    exchange->pending = GNUNET_YES;
-    if (NULL != exchange->wire_request)
-    {
-      TALER_EXCHANGE_wire_cancel (exchange->wire_request);
-      exchange->wire_request = NULL;
-    }
-    if (NULL != exchange->wire_task)
-    {
-      GNUNET_SCHEDULER_cancel (exchange->wire_task);
-      exchange->wire_task = NULL;
-    }
-    while (NULL != (fo = exchange->fo_head))
-    {
-      fo->fc (fo->fc_cls,
-              hr,
-              NULL,
-              NULL,
-              GNUNET_NO);
-      TMH_EXCHANGES_find_exchange_cancel (fo);
-    }
-    if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat)
-    {
-      /* Log hard error: we likely need admin help! */
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Exchange `%s' runs an incompatible more recent version of 
the Taler protocol. Will not retry. This client may need to be updated.\n",
-                  exchange->url);
-      /* Theoretically, the exchange could downgrade,
-         but let's not be too aggressive about retries
-         on this one. */
-      exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS,
-                                                        exchange->retry_delay);
-    }
-    exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Failed to fetch /keys from `%s': %d/%u, retrying in %s\n",
-                exchange->url,
-                (int) hr->ec,
-                hr->http_status,
-                GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay,
-                                                        GNUNET_YES));
-    GNUNET_assert (NULL == exchange->retry_task);
-    exchange->first_retry = GNUNET_TIME_relative_to_absolute (
-      exchange->retry_delay);
-    exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
-                                                         &retry_exchange,
-                                                         exchange);
+    fail_and_retry (exchange,
+                    hr,
+                    compat);
     return;
   }
-  if ( (GNUNET_YES == exchange->trusted) &&
+  if ( (exchange->trusted) &&
        (0 != GNUNET_memcmp (&exchange->master_pub,
                             &keys->master_pub)) )
   {
@@ -812,9 +851,9 @@ keys_mgmt_cb (void *cls,
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Master public key of exchange `%s' differs from our 
configuration. Not trusting exchange.\n",
                 exchange->url);
-    exchange->trusted = GNUNET_NO;
+    exchange->trusted = false;
   }
-  if (GNUNET_NO == exchange->trusted)
+  if (! exchange->trusted)
     exchange->master_pub = keys->master_pub;
 
   if (0 != (TALER_EXCHANGE_VC_NEWER & compat))
@@ -830,6 +869,32 @@ keys_mgmt_cb (void *cls,
                   exchange->url);
     }
   }
+
+  /* store exchange online signing keys in our DB */
+  for (unsigned int i = 0; i<keys->num_sign_keys; i++)
+  {
+    struct TALER_EXCHANGE_SigningPublicKey *sign_key = &keys->sign_keys[i];
+    enum GNUNET_DB_QueryStatus qs;
+
+    TMH_db->preflight (TMH_db->cls);
+    qs = TMH_db->insert_exchange_signkey (TMH_db->cls,
+                                          &keys->master_pub,
+                                          &sign_key->key,
+                                          sign_key->valid_from,
+                                          sign_key->valid_until,
+                                          sign_key->valid_legal,
+                                          &sign_key->master_sig);
+    /* 0 is OK, we may already have the key in the DB! */
+    if (0 > qs)
+    {
+      GNUNET_break (0);
+      fail_and_retry (exchange,
+                      hr,
+                      compat);
+      return;
+    }
+  }
+
   expire = TALER_EXCHANGE_check_keys_current (exchange->conn,
                                               GNUNET_NO,
                                               GNUNET_NO);
@@ -845,9 +910,8 @@ keys_mgmt_cb (void *cls,
     = GNUNET_SCHEDULER_add_delayed (delay,
                                     &retry_exchange,
                                     exchange);
-  exchange->pending = GNUNET_NO;
-  if ( (GNUNET_YES ==
-        process_find_operations (exchange)) &&
+  exchange->pending = false;
+  if ( (process_find_operations (exchange)) &&
        (NULL == exchange->wire_request) &&
        (NULL == exchange->wire_task) )
   {
@@ -872,10 +936,9 @@ return_result (void *cls)
   struct Exchange *exchange = fo->my_exchange;
 
   fo->at = NULL;
-  if ( (GNUNET_YES ==
-        process_find_operations (exchange)) &&
+  if ( (process_find_operations (exchange)) &&
        (NULL == exchange->wire_request) &&
-       (GNUNET_NO == exchange->pending) &&
+       (! exchange->pending) &&
        (NULL != exchange->wire_task) )
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -945,7 +1008,7 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
     /* This is a new exchange */
     exchange = GNUNET_new (struct Exchange);
     exchange->url = GNUNET_strdup (chosen_exchange);
-    exchange->pending = GNUNET_YES;
+    exchange->pending = true;
     GNUNET_CONTAINER_DLL_insert (exchange_head,
                                  exchange_tail,
                                  exchange);
@@ -984,7 +1047,7 @@ TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
   }
 
 
-  if ( (GNUNET_YES != exchange->pending) &&
+  if ( (! exchange->pending) &&
        ( (NULL == fo->wire_method) ||
          (NULL != get_wire_fees (exchange,
                                  now,
@@ -1001,14 +1064,14 @@ TMH_EXCHANGES_find_exchange (const char 
*chosen_exchange,
   /* If new or resumed, (re)try fetching /keys */
   if ( (NULL == exchange->conn) &&
        (NULL == exchange->retry_task) &&
-       (GNUNET_YES == exchange->pending) )
+       (exchange->pending) )
   {
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Do not have current key data. Will request /keys now\n");
     exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
                                                      exchange);
   }
-  else if ( (GNUNET_NO == exchange->pending) &&
+  else if ( (! exchange->pending) &&
             (NULL == exchange->wire_task) &&
             (NULL == exchange->wire_request) )
   {
@@ -1039,7 +1102,7 @@ TMH_EXCHANGES_find_exchange_cancel (struct 
TMH_EXCHANGES_FindOperation *fo)
   GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
                                exchange->fo_tail,
                                fo);
-  GNUNET_free_non_null (fo->wire_method);
+  GNUNET_free (fo->wire_method);
   GNUNET_free (fo);
 }
 
@@ -1112,7 +1175,7 @@ accept_exchanges (void *cls,
                                                     &exchange->master_pub.
                                                     eddsa_pub))
     {
-      exchange->trusted = GNUNET_YES;
+      exchange->trusted = true;
     }
     else
     {
@@ -1133,7 +1196,7 @@ accept_exchanges (void *cls,
   GNUNET_CONTAINER_DLL_insert (exchange_head,
                                exchange_tail,
                                exchange);
-  exchange->pending = GNUNET_YES;
+  exchange->pending = true;
   GNUNET_assert (NULL == exchange->retry_task);
   exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
                                                    exchange);
@@ -1173,7 +1236,7 @@ TMH_EXCHANGES_init (const struct 
GNUNET_CONFIGURATION_Handle *cfg)
   {
     json_t *j_exchange;
 
-    if (GNUNET_YES != exchange->trusted)
+    if (! exchange->trusted)
       continue;
     j_exchange = json_pack ("{s:s, s:o}",
                             "url", exchange->url,
@@ -1215,6 +1278,7 @@ TMH_EXCHANGES_done ()
         GNUNET_free (af);
       }
       GNUNET_free (f->wire_method);
+      GNUNET_free (f->payto_uri);
       GNUNET_free (f);
     }
     if (NULL != exchange->wire_request)
diff --git a/src/backend/taler-merchant-httpd_exchanges.h 
b/src/backend/taler-merchant-httpd_exchanges.h
index 3a47408..4fcb812 100644
--- a/src/backend/taler-merchant-httpd_exchanges.h
+++ b/src/backend/taler-merchant-httpd_exchanges.h
@@ -61,15 +61,17 @@ TMH_EXCHANGES_done (void);
  * @param cls closure
  * @param hr HTTP response details
  * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
  * @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
+ * @param exchange_trusted true if this exchange is trusted by config
  */
 typedef void
 (*TMH_EXCHANGES_FindContinuation)(void *cls,
                                   const struct TALER_EXCHANGE_HttpResponse *hr,
                                   struct TALER_EXCHANGE_Handle *eh,
+                                  const char *payto_uri,
                                   const struct TALER_Amount *wire_fee,
-                                  int exchange_trusted);
+                                  bool exchange_trusted);
 
 
 /**
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.c 
b/src/backend/taler-merchant-httpd_get-orders-ID.c
new file mode 100644
index 0000000..826c530
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.c
@@ -0,0 +1,1069 @@
+/*
+  This file is part of TALER
+  (C) 2014-2020 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_get-orders-ID.c
+ * @brief implementation of GET /orders/$ID
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_exchange_service.h>
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_get-orders-ID.h"
+
+/**
+ * How often do we retry DB transactions on serialization failures?
+ */
+#define MAX_RETRIES 5
+
+/**
+ * Information we keep for each coin to be refunded.
+ */
+struct CoinRefund
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct CoinRefund *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct CoinRefund *prev;
+
+  /**
+   * Request to connect to the target exchange.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Handle for the refund operation with the exchange.
+   */
+  struct TALER_EXCHANGE_RefundHandle *rh;
+
+  /**
+   * Request this operation is part of.
+   */
+  struct GetOrderData *god;
+
+  /**
+   * URL of the exchange for this @e coin_pub.
+   */
+  char *exchange_url;
+
+  /**
+   * Fully reply from the exchange, only possibly set if
+   * we got a JSON reply and a non-#MHD_HTTP_OK error code
+   */
+  json_t *exchange_reply;
+
+  /**
+   * When did the merchant grant the refund. To be used to group events
+   * in the wallet.
+   */
+  struct GNUNET_TIME_Absolute execution_time;
+
+  /**
+   * Coin to refund.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Refund transaction ID to use.
+   */
+  uint64_t rtransaction_id;
+
+  /**
+   * Unique serial number identifying the refund.
+   */
+  uint64_t refund_serial;
+
+  /**
+   * Amount to refund.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Public key of the exchange affirming the refund.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Signature of the exchange affirming the refund.
+   */
+  struct TALER_ExchangeSignatureP exchange_sig;
+
+  /**
+   * HTTP status from the exchange, #MHD_HTTP_OK if
+   * @a exchange_pub and @a exchange_sig are valid.
+   */
+  unsigned int exchange_status;
+
+  /**
+   * HTTP error code from the exchange.
+   */
+  enum TALER_ErrorCode exchange_code;
+
+};
+
+
+/**
+ * Context for the operation.
+ */
+struct GetOrderData
+{
+
+  /**
+   * Hashed version of contract terms.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * DLL of (suspended) requests.
+   */
+  struct GetOrderData *next;
+
+  /**
+   * DLL of (suspended) requests.
+   */
+  struct GetOrderData *prev;
+
+  /**
+   * Refunds for this order. Head of DLL.
+   */
+  struct CoinRefund *cr_head;
+
+  /**
+   * Refunds for this order. Tail of DLL.
+   */
+  struct CoinRefund *cr_tail;
+
+  /**
+   * Context of the request.
+   */
+  struct TMH_HandlerContext *hc;
+
+  /**
+   * Entry in the #resume_timeout_heap for this check payment, if we are
+   * suspended.
+   */
+  struct TMH_SuspendedConnection sc;
+
+  /**
+   * Which merchant instance is this for?
+   */
+  struct MerchantInstance *mi;
+
+  /**
+   * order ID for the payment
+   */
+  const char *order_id;
+
+  /**
+   * Where to get the contract
+   */
+  const char *contract_url;
+
+  /**
+   * fulfillment URL of the contract (valid as long as
+   * @e contract_terms is valid).
+   */
+  const char *fulfillment_url;
+
+  /**
+   * session of the client
+   */
+  const char *session_id;
+
+  /**
+   * Contract terms of the payment we are checking. NULL when they
+   * are not (yet) known.
+   */
+  json_t *contract_terms;
+
+  /**
+   * Total refunds granted for this payment. Only initialized
+   * if @e refunded is set to true.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Did we suspend @a connection?
+   */
+  bool suspended;
+
+  /**
+   * Return code: #TALER_EC_NONE if successful.
+   */
+  enum TALER_ErrorCode ec;
+
+  /**
+   * Set to true if this payment has been refunded and
+   * @e refund_amount is initialized.
+   */
+  bool refunded;
+
+};
+
+
+/**
+ * Head of DLL of (suspended) requests.
+ */
+static struct GetOrderData *god_head;
+
+/**
+ * Tail of DLL of (suspended) requests.
+ */
+static struct GetOrderData *god_tail;
+
+
+/**
+ * Force resuming all suspended order lookups, needed during shutdown.
+ */
+void
+TMH_force_wallet_get_order_resume (void)
+{
+  struct GetOrderData *god;
+
+  while (NULL != (god = god_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (god_head,
+                                 god_tail,
+                                 god);
+    GNUNET_assert (god->suspended);
+    god->suspended = false;
+    MHD_resume_connection (god->sc.con);
+  }
+}
+
+
+/**
+ * Suspend this @a god until the trigger is satisfied.
+ *
+ * @param god request to suspend
+ */
+static void
+suspend_god (struct GetOrderData *god)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Suspending GET /orders/%s\n",
+              god->order_id);
+  TMH_long_poll_suspend (god->order_id,
+                         god->hc->instance,
+                         &god->sc,
+                         (god->sc.awaiting_refund)
+                         ? &god->sc.refund_expected
+                         : NULL);
+}
+
+
+/**
+ * Create a taler://pay/ URI for the given @a con and @a order_id
+ * and @a session_id and @a instance_id.
+ *
+ * @param con HTTP connection
+ * @param order_id the order id
+ * @param session_id session, may be NULL
+ * @param instance_id instance, may be "default"
+ * @return corresponding taler://pay/ URI, or NULL on missing "host"
+ */
+char *
+TMH_make_taler_pay_uri (struct MHD_Connection *con,
+                        const char *order_id,
+                        const char *session_id,
+                        const char *instance_id)
+{
+  const char *host;
+  const char *forwarded_host;
+  const char *uri_path;
+  const char *uri_instance_id;
+  const char *query;
+  char *result;
+
+  host = MHD_lookup_connection_value (con,
+                                      MHD_HEADER_KIND,
+                                      "Host");
+  forwarded_host = MHD_lookup_connection_value (con,
+                                                MHD_HEADER_KIND,
+                                                "X-Forwarded-Host");
+
+  uri_path = MHD_lookup_connection_value (con,
+                                          MHD_HEADER_KIND,
+                                          "X-Forwarded-Prefix");
+  if (NULL == uri_path)
+    uri_path = "-";
+  if (NULL != forwarded_host)
+    host = forwarded_host;
+  if (0 == strcmp (instance_id,
+                   "default"))
+    uri_instance_id = "-";
+  else
+    uri_instance_id = instance_id;
+  if (NULL == host)
+  {
+    /* Should never happen, at least the host header should be defined */
+    GNUNET_break (0);
+    return NULL;
+  }
+
+  if (GNUNET_YES == TALER_mhd_is_https (con))
+    query = "";
+  else
+    query = "?insecure=1";
+  GNUNET_assert (NULL != order_id);
+  GNUNET_assert (0 < GNUNET_asprintf (&result,
+                                      "taler://pay/%s/%s/%s/%s%s%s%s",
+                                      host,
+                                      uri_path,
+                                      uri_instance_id,
+                                      order_id,
+                                      (NULL == session_id) ? "" : "/",
+                                      (NULL == session_id) ? "" : session_id,
+                                      query));
+  return result;
+}
+
+
+/**
+ * The client did not yet pay, send it the payment request.
+ *
+ * @param god check pay request context
+ * @param already_paid_order_id if for the fulfillment URI there is
+ *          already a paid order, this is the order ID to redirect
+ *          the wallet to; NULL if not applicable
+ * @return #MHD_YES on success
+ */
+static MHD_RESULT
+send_pay_request (struct GetOrderData *god,
+                  const char *already_paid_order_id)
+{
+  MHD_RESULT ret;
+  char *taler_pay_uri;
+  struct GNUNET_TIME_Relative remaining;
+
+  remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
+  if (0 != remaining.rel_value_us)
+  {
+    /* long polling: do not queue a response, suspend connection instead */
+    suspend_god (god);
+    return MHD_YES;
+  }
+
+  /* Check if resource_id has been paid for in the same session
+   * with another order_id.
+   */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Sending payment request in /poll-payment\n");
+  taler_pay_uri = TMH_make_taler_pay_uri (god->sc.con,
+                                          god->order_id,
+                                          god->session_id,
+                                          god->hc->instance->settings.id);
+  ret = TALER_MHD_reply_json_pack (god->sc.con,
+                                   MHD_HTTP_OK,
+                                   "{s:s, s:b, s:s?}",
+                                   "taler_pay_uri", taler_pay_uri,
+                                   "paid", false,
+                                   "already_paid_order_id",
+                                   already_paid_order_id);
+  GNUNET_free (taler_pay_uri);
+  return ret;
+}
+
+
+/**
+ * Check if @a god has sub-activities still pending.
+ *
+ * @param god request to check
+ * @return true if activities are still pending
+ */
+static bool
+god_pending (struct GetOrderData *god)
+{
+  for (struct CoinRefund *cr = god->cr_head;
+       NULL != cr;
+       cr = cr->next)
+  {
+    if ( (NULL != cr->fo) ||
+         (NULL != cr->rh) )
+      return true;
+  }
+  return false;
+}
+
+
+/**
+ * Check if @a god is ready to be resumed, and if so, do it.
+ *
+ * @param god refund request to be possibly ready
+ */
+static void
+check_resume_god (struct GetOrderData *god)
+{
+  if (god_pending (god))
+    return;
+  GNUNET_CONTAINER_DLL_remove (god_head,
+                               god_tail,
+                               god);
+  GNUNET_assert (god->suspended);
+  god->suspended = false;
+  MHD_resume_connection (god->sc.con);
+  TMH_trigger_daemon ();
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * refund request to an exchange.
+ *
+ * @param cls a `struct CoinRefund`
+ * @param hr HTTP response data
+ * @param refund_fee fee the exchange charged
+ * @param exchange_pub exchange key used to sign refund confirmation
+ * @param exchange_sig exchange's signature over refund
+ */
+static void
+refund_cb (void *cls,
+           const struct TALER_EXCHANGE_HttpResponse *hr,
+           const struct TALER_Amount *refund_fee,
+           const struct TALER_ExchangePublicKeyP *exchange_pub,
+           const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+  struct CoinRefund *cr = cls;
+
+  cr->rh = NULL;
+  cr->exchange_status = hr->http_status;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Exchange refund status for coin %s is %u\n",
+              TALER_B2S (&cr->coin_pub),
+              hr->http_status);
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    cr->exchange_code = hr->ec;
+    cr->exchange_reply = json_incref ((json_t*) hr->reply);
+  }
+  else
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    cr->exchange_pub = *exchange_pub;
+    cr->exchange_sig = *exchange_sig;
+    qs = TMH_db->insert_refund_proof (TMH_db->cls,
+                                      cr->refund_serial,
+                                      refund_fee,
+                                      exchange_sig,
+                                      exchange_pub);
+    if (0 >= qs)
+    {
+      /* generally, this is relatively harmless for the merchant, but let's at
+         least log this. */
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to persist exchange response to /refund in database: 
%d\n",
+                  qs);
+    }
+  }
+  check_resume_god (cr->god);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @param cls a `struct CoinRefund *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+exchange_found_cb (void *cls,
+                   const struct TALER_EXCHANGE_HttpResponse *hr,
+                   struct TALER_EXCHANGE_Handle *eh,
+                   const char *payto_uri,
+                   const struct TALER_Amount *wire_fee,
+                   bool exchange_trusted)
+{
+  struct CoinRefund *cr = cls;
+
+  (void) payto_uri;
+  cr->fo = NULL;
+  if (TALER_EC_NONE == hr->ec)
+  {
+    cr->rh = TALER_EXCHANGE_refund (eh,
+                                    &cr->refund_amount,
+                                    &cr->god->h_contract_terms,
+                                    &cr->coin_pub,
+                                    cr->rtransaction_id,
+                                    &cr->god->hc->instance->merchant_priv,
+                                    &refund_cb,
+                                    cr);
+    return;
+  }
+  cr->exchange_status = hr->http_status;
+  cr->exchange_code = hr->ec;
+  cr->exchange_reply = json_incref ((json_t*) hr->reply);
+  check_resume_god (cr->god);
+}
+
+
+/**
+ * Function called with detailed information about a refund.
+ * It is responsible for packing up the data to return.
+ *
+ * @param cls closure
+ * @param refund_serial unique serial number of the refund
+ * @param timestamp time of the refund (for grouping of refunds in the wallet 
UI)
+ * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explanation of the refund
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ */
+static void
+process_refunds_cb (void *cls,
+                    uint64_t refund_serial,
+                    struct GNUNET_TIME_Absolute timestamp,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
+                    uint64_t rtransaction_id,
+                    const char *reason,
+                    const struct TALER_Amount *refund_amount)
+{
+  struct GetOrderData *god = cls;
+  struct CoinRefund *cr;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Found refund of %s for coin %s with reason `%s' in database\n",
+              TALER_B2S (coin_pub),
+              TALER_amount2s (refund_amount),
+              reason);
+  cr = GNUNET_new (struct CoinRefund);
+  cr->refund_serial = refund_serial;
+  cr->exchange_url = GNUNET_strdup (exchange_url);
+  cr->god = god;
+  cr->coin_pub = *coin_pub;
+  cr->rtransaction_id = rtransaction_id;
+  cr->refund_amount = *refund_amount;
+  cr->execution_time = timestamp;
+  GNUNET_CONTAINER_DLL_insert (god->cr_head,
+                               god->cr_tail,
+                               cr);
+  if (god->refunded)
+  {
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&god->refund_amount,
+                                     &god->refund_amount,
+                                     refund_amount));
+    return;
+  }
+  god->refund_amount = *refund_amount;
+  god->refunded = true;
+}
+
+
+/**
+ * Clean up refund processing.
+ *
+ * @param god handle to clean up refund processing for
+ */
+static void
+rf_cleanup (struct GetOrderData *god)
+{
+  struct CoinRefund *cr;
+
+  while (NULL != (cr = god->cr_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (god->cr_head,
+                                 god->cr_tail,
+                                 cr);
+    if (NULL != cr->fo)
+    {
+      TMH_EXCHANGES_find_exchange_cancel (cr->fo);
+      cr->fo = NULL;
+    }
+    if (NULL != cr->rh)
+    {
+      TALER_EXCHANGE_refund_cancel (cr->rh);
+      cr->rh = NULL;
+    }
+    if (NULL != cr->exchange_reply)
+    {
+      json_decref (cr->exchange_reply);
+      cr->exchange_reply = NULL;
+    }
+    GNUNET_free (cr->exchange_url);
+    GNUNET_free (cr);
+  }
+}
+
+
+/**
+ * Clean up the session state for a GET /orders/$ID request.
+ *
+ * @param cls must be a `struct GetOrderData *`
+ */
+static void
+god_cleanup (void *cls)
+{
+  struct GetOrderData *god = cls;
+
+  rf_cleanup (god);
+  if (NULL != god->contract_terms)
+    json_decref (god->contract_terms);
+  GNUNET_free (god);
+}
+
+
+/**
+ * Handle a GET "/orders/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
+                   struct MHD_Connection *connection,
+                   struct TMH_HandlerContext *hc)
+{
+  struct GetOrderData *god = hc->ctx;
+  const char *order_id = hc->infix;
+  enum GNUNET_DB_QueryStatus qs;
+
+  if (NULL == god)
+  {
+    god = GNUNET_new (struct GetOrderData);
+    hc->ctx = god;
+    hc->cc = &god_cleanup;
+    god->sc.con = connection;
+    god->hc = hc;
+    god->order_id = order_id;
+
+    {
+      const char *cts;
+
+      cts = MHD_lookup_connection_value (connection,
+                                         MHD_GET_ARGUMENT_KIND,
+                                         "h_contract");
+      if (NULL == cts)
+      {
+        /* h_contract required but missing */
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MISSING,
+                                           "h_contract required");
+      }
+      if (GNUNET_OK !=
+          GNUNET_CRYPTO_hash_from_string (cts,
+                                          &god->h_contract_terms))
+      {
+        /* cts has wrong encoding */
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "h_contract malformed");
+      }
+    }
+
+    {
+      const char *long_poll_timeout_s;
+
+      long_poll_timeout_s = MHD_lookup_connection_value (connection,
+                                                         MHD_GET_ARGUMENT_KIND,
+                                                         "timeout");
+      if (NULL != long_poll_timeout_s)
+      {
+        unsigned int timeout;
+
+        if (1 != sscanf (long_poll_timeout_s,
+                         "%u",
+                         &timeout))
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             TALER_EC_PARAMETER_MALFORMED,
+                                             "timeout must be non-negative 
number");
+        }
+        god->sc.long_poll_timeout
+          = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+                                                GNUNET_TIME_UNIT_SECONDS,
+                                                timeout));
+      }
+      else
+      {
+        god->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
+      }
+    }
+
+    {
+      const char *min_refund;
+
+      min_refund = MHD_lookup_connection_value (connection,
+                                                MHD_GET_ARGUMENT_KIND,
+                                                "refund");
+      if (NULL != min_refund)
+      {
+        if ( (GNUNET_OK !=
+              TALER_string_to_amount (min_refund,
+                                      &god->sc.refund_expected)) ||
+             (0 != strcasecmp (god->sc.refund_expected.currency,
+                               TMH_currency) ) )
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             TALER_EC_PARAMETER_MALFORMED,
+                                             "invalid amount given for refund 
argument");
+        }
+        god->sc.awaiting_refund = true;
+      }
+    }
+
+    god->session_id = MHD_lookup_connection_value (connection,
+                                                   MHD_GET_ARGUMENT_KIND,
+                                                   "session_id");
+
+    /* Convert order_id to h_contract_terms */
+    TMH_db->preflight (TMH_db->cls);
+    {
+      uint64_t order_serial;
+
+      qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                          hc->instance->settings.id,
+                                          order_id,
+                                          &god->contract_terms,
+                                          &order_serial);
+    }
+    if (0 > qs)
+    {
+      /* single, read-only SQL statements should never cause
+         serialization problems */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR,
+                                         "database error looking up contract");
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Unknown order id given: `%s'\n",
+                  order_id);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_GET_ORDERS_ID_UNKNOWN,
+                                         "order_id not found in database");
+    }
+
+    /* Check client provided the right hash code of the contract terms */
+    {
+      struct GNUNET_HashCode h;
+
+      if (GNUNET_OK !=
+          TALER_JSON_hash (god->contract_terms,
+                           &h))
+      {
+        GNUNET_break (0);
+        GNUNET_free (god);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_INTERNAL_LOGIC_ERROR,
+                                           "Could not hash contract terms");
+      }
+      if (0 !=
+          GNUNET_memcmp (&h,
+                         &god->h_contract_terms))
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_FORBIDDEN,
+                                           TALER_EC_GET_ORDER_WRONG_CONTRACT,
+                                           "Contract hash does not match 
order");
+      }
+    }
+
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_string ("fulfillment_url",
+                                 &god->fulfillment_url),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (god->contract_terms,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR,
+                                           "Merchant database error (contract 
terms corrupted)");
+      }
+    }
+  } /* end of first-time initialization / sanity checks */
+
+  if ( (NULL != god->session_id) &&
+       (NULL != god->fulfillment_url) )
+  {
+    /* Check if paid within a session. */
+    char *already_paid_order_id = NULL;
+
+    qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
+                                              hc->instance->settings.id,
+                                              god->fulfillment_url,
+                                              god->session_id,
+                                              &already_paid_order_id);
+    if (qs < 0)
+    {
+      /* single, read-only SQL statements should never cause
+         serialization problems */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR,
+                                         "db error fetching pay session info");
+    }
+    if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+         (0 != strcmp (order_id,
+                       already_paid_order_id)) )
+    {
+      MHD_RESULT ret;
+
+      ret = send_pay_request (god,
+                              already_paid_order_id);
+      GNUNET_free (already_paid_order_id);
+      return ret;
+    }
+    GNUNET_break (1 == qs);
+    GNUNET_free (already_paid_order_id);
+  }
+  else
+  {
+    /* Check if paid regardless of session. */
+    struct GNUNET_HashCode h_contract;
+    bool paid;
+
+    qs = TMH_db->lookup_order_status (TMH_db->cls,
+                                      hc->instance->settings.id,
+                                      order_id,
+                                      &h_contract,
+                                      &paid);
+    if (0 >= qs)
+    {
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR,
+                                         "Merchant database error");
+    }
+    GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+    GNUNET_break (0 ==
+                  GNUNET_memcmp (&h_contract,
+                                 &god->h_contract_terms));
+    if (! paid)
+    {
+      return send_pay_request (god,
+                               NULL);
+    }
+  }
+
+  /* At this point, we know the contract was paid. Let's check for
+     refunds. First, clear away refunds found from previous invocations. */
+  rf_cleanup (god);
+  GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (TMH_currency,
+                                                     &god->refund_amount));
+  qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
+                                        hc->instance->settings.id,
+                                        &god->h_contract_terms,
+                                        &process_refunds_cb,
+                                        god);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR,
+                                       "Failed to lookup refunds for 
contract");
+  }
+
+  /* Now launch exchange interactions, unless we already have the
+     response in the database! */
+  for (struct CoinRefund *cr = god->cr_head;
+       NULL != cr;
+       cr = cr->next)
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = TMH_db->lookup_refund_proof (TMH_db->cls,
+                                      cr->refund_serial,
+                                      &cr->exchange_sig,
+                                      &cr->exchange_pub);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR,
+                                         "Merchant database error");
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      /* We need to talk to the exchange */
+      cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
+                                            NULL,
+                                            GNUNET_NO,
+                                            &exchange_found_cb,
+                                            cr);
+      break;
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      /* We got a reply earlier, set status accordingly */
+      cr->exchange_status = MHD_HTTP_OK;
+      break;
+    }
+  }
+
+  if ( (god->sc.awaiting_refund) &&
+       ( (! god->refunded) ||
+         (1 != TALER_amount_cmp (&god->refund_amount,
+                                 &god->sc.refund_expected)) ) )
+  {
+    /* Client is waiting for a refund larger than what we have, suspend
+       until timeout */
+    struct GNUNET_TIME_Relative remaining;
+
+    remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
+    if (0 != remaining.rel_value_us)
+    {
+      /* yes, indeed suspend */
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Awaiting refund exceeding %s\n",
+                  TALER_amount2s (&god->sc.refund_expected));
+      suspend_god (god);
+      return MHD_YES;
+    }
+  }
+
+  /* Check if there are still exchange operations pending */
+  if (god_pending (god))
+  {
+    if (! god->suspended)
+    {
+      god->suspended = true;
+      MHD_suspend_connection (connection);
+      GNUNET_CONTAINER_DLL_insert (god_head,
+                                   god_tail,
+                                   god);
+    }
+    return MHD_YES;   /* we're still talking to the exchange */
+  }
+
+  /* All operations done, build final response */
+  {
+    json_t *ra;
+
+    ra = json_array ();
+    GNUNET_assert (NULL != ra);
+    for (struct CoinRefund *cr = god->cr_head;
+         NULL != cr;
+         cr = cr->next)
+    {
+      json_t *refund;
+
+      if (MHD_HTTP_OK != cr->exchange_status)
+      {
+        if (NULL == cr->exchange_reply)
+        {
+          refund = json_pack ("{s:I,s:I,s:o,s:o}"
+                              "exchange_status",
+                              (json_int_t) cr->exchange_status,
+                              "rtransaction_id",
+                              (json_int_t) cr->rtransaction_id,
+                              "coin_pub",
+                              GNUNET_JSON_from_data_auto (&cr->coin_pub),
+                              "refund_amount",
+                              TALER_JSON_from_amount (&cr->refund_amount));
+        }
+        else
+        {
+          refund = json_pack ("{s:I,s:I,s:o,s:I,s:o,s:o}"
+                              "exchange_status",
+                              (json_int_t) cr->exchange_status,
+                              "exchange_code",
+                              (json_int_t) cr->exchange_code,
+                              "exchange_reply",
+                              cr->exchange_reply,
+                              "rtransaction_id",
+                              (json_int_t) cr->rtransaction_id,
+                              "coin_pub",
+                              GNUNET_JSON_from_data_auto (&cr->coin_pub),
+                              "refund_amount",
+                              TALER_JSON_from_amount (&cr->refund_amount));
+        }
+      }
+      else
+      {
+        refund = json_pack ("{s:I,s:o,s:o,s:I,s:o,s:o}",
+                            "exchange_status",
+                            (json_int_t) cr->exchange_status,
+                            "exchange_sig",
+                            GNUNET_JSON_from_data_auto (&cr->exchange_sig),
+                            "exchange_pub",
+                            GNUNET_JSON_from_data_auto (&cr->exchange_pub),
+                            "rtransaction_id",
+                            (json_int_t) cr->rtransaction_id,
+                            "coin_pub",
+                            GNUNET_JSON_from_data_auto (&cr->coin_pub),
+                            "refund_amount",
+                            TALER_JSON_from_amount (&cr->refund_amount),
+                            "exchange_http_status");
+      }
+      GNUNET_assert (
+        0 ==
+        json_array_append_new (ra,
+                               refund));
+    }
+
+    return TALER_MHD_reply_json_pack (
+      connection,
+      MHD_HTTP_OK,
+      "{s:b, s:b, s:o, s:o, s:o}",
+      "paid", true,
+      "refunded", god->refunded,
+      "refund_amount",
+      TALER_JSON_from_amount (&god->refund_amount),
+      "refunds",
+      ra,
+      "merchant_pub",
+      GNUNET_JSON_from_data_auto (&hc->instance->merchant_pub));
+  }
+}
+
+
+/* end of taler-merchant-httpd_get-orders-ID.c */
diff --git a/src/backend/taler-merchant-httpd_get-orders-ID.h 
b/src/backend/taler-merchant-httpd_get-orders-ID.h
new file mode 100644
index 0000000..1d2d61f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_get-orders-ID.h
@@ -0,0 +1,57 @@
+/*
+  This file is part of TALER
+  (C) 2014, 2015, 2016, 2017, 2020 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_get-orders-ID.h
+ * @brief implementation of GET /orders/$ID
+ * @author Marcello Stanisci
+ */
+#ifndef TALER_MERCHANT_HTTPD_GET_ORDERS_ID_H
+#define TALER_MERCHANT_HTTPD_GET_ORDERS_ID_H
+#include <microhttpd.h>
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Create a taler://pay/ URI for the given @a con and @a order_id
+ * and @a session_id and @a instance_id.
+ *
+ * @param con HTTP connection
+ * @param order_id the order id
+ * @param session_id session, may be NULL
+ * @param instance_id instance, may be "default"
+ * @return corresponding taler://pay/ URI, or NULL on missing "host"
+ */
+char *
+TMH_make_taler_pay_uri (struct MHD_Connection *con,
+                        const char *order_id,
+                        const char *session_id,
+                        const char *instance_id);
+
+
+/**
+ * Handle a GET "/orders/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
+                   struct MHD_Connection *connection,
+                   struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_get-tips-ID.c 
b/src/backend/taler-merchant-httpd_get-tips-ID.c
new file mode 100644
index 0000000..52a0a56
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_get-tips-ID.c
@@ -0,0 +1,123 @@
+/*
+  This file is part of TALER
+  (C) 2014-2020 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_get-tips-ID.c
+ * @brief implementation of GET /tips/$ID
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_get-tips-ID.h"
+
+
+/**
+ * Handle a GET "/tips/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_get_tips_ID (const struct TMH_RequestHandler *rh,
+                 struct MHD_Connection *connection,
+                 struct TMH_HandlerContext *hc)
+{
+  struct GNUNET_HashCode tip_id;
+  enum GNUNET_DB_QueryStatus qs;
+  struct TALER_Amount total_authorized;
+  struct TALER_Amount total_picked_up;
+  struct GNUNET_TIME_Absolute expiration;
+  char *exchange_url;
+  struct TALER_ReservePrivateKeyP reserve_priv;
+
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_hash_from_string (hc->infix,
+                                      &tip_id))
+  {
+    /* tip_id has wrong encoding */
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "tip_id malformed");
+  }
+
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->lookup_tip (TMH_db->cls,
+                           hc->instance->settings.id,
+                           &tip_id,
+                           &total_authorized,
+                           &total_picked_up,
+                           &expiration,
+                           &exchange_url,
+                           &reserve_priv);
+  if (0 > qs)
+  {
+    /* single, read-only SQL statements should never cause
+       serialization problems */
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    /* Always report on hard error as well to enable diagnostics */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GET_TIPS_DB_LOOKUP_ERROR,
+                                       "database error looking up contract");
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Unknown tip id given: `%s'\n",
+                hc->infix);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_GET_TIPS_ID_UNKNOWN,
+                                       "tip_id not found in database");
+  }
+
+  /* Build response */
+  {
+    MHD_RESULT ret;
+    struct TALER_Amount remaining;
+    struct GNUNET_TIME_Absolute expiration_round = expiration;
+
+    GNUNET_break (0 <=
+                  TALER_amount_subtract (&remaining,
+                                         &total_authorized,
+                                         &total_picked_up));
+
+    GNUNET_TIME_round_abs (&expiration_round);
+
+    ret = TALER_MHD_reply_json_pack (connection,
+                                     MHD_HTTP_OK,
+                                     "{s:s, s:o, s:o}",
+                                     "exchange_url",
+                                     exchange_url,
+                                     "tip_amount",
+                                     TALER_JSON_from_amount (&remaining),
+                                     "expiration",
+                                     GNUNET_JSON_from_time_abs (
+                                       expiration_round));
+    GNUNET_free (exchange_url);
+    return ret;
+  }
+}
+
+
+/* end of taler-merchant-httpd_get-tips-ID.c */
diff --git a/src/backend/taler-merchant-httpd_refund_lookup.h 
b/src/backend/taler-merchant-httpd_get-tips-ID.h
similarity index 52%
copy from src/backend/taler-merchant-httpd_refund_lookup.h
copy to src/backend/taler-merchant-httpd_get-tips-ID.h
index 24495da..6fa0253 100644
--- a/src/backend/taler-merchant-httpd_refund_lookup.h
+++ b/src/backend/taler-merchant-httpd_get-tips-ID.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015, 2016, 2017 Taler Systems SA
+  (C) 2020 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
@@ -13,36 +13,28 @@
   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_refund_lookup.h
- * @brief
+ * @file backend/taler-merchant-httpd_get-tips-ID.h
+ * @brief implementation of GET /tips/$ID
  * @author Marcello Stanisci
  */
-
-#ifndef TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
-#define TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
+#ifndef TALER_MERCHANT_HTTPD_GET_TIPS_ID_H
+#define TALER_MERCHANT_HTTPD_GET_TIPS_ID_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Return refund situation about a contract.
+ * Handle a GET "/tips/$ID" 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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
-                          struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi);
+TMH_get_tips_ID (const struct TMH_RequestHandler *rh,
+                 struct MHD_Connection *connection,
+                 struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_history.c 
b/src/backend/taler-merchant-httpd_history.c
deleted file mode 100644
index dd35320..0000000
--- a/src/backend/taler-merchant-httpd_history.c
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014, 2015, 2016, 2017 INRIA
-
-  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_history.c
- * @brief HTTP serving layer mainly intended to communicate with the frontend
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * Closure for #pd_cb.
- */
-struct ProcessContractClosure
-{
-
-  /**
-   * Updated by #pd_cb to build the response.
-   */
-  json_t *response;
-
-  /**
-   * Set to #GNUNET_SYSERR if the database returned a contract
-   * that was not well-formed.
-   */
-  int failure;
-
-};
-
-
-/**
- * Function called with information about a transaction.
- *
- * @param cls closure of type `struct ProcessContractClosure`
- * @param order_id transaction's order ID.
- * @param row_id serial numer of the transaction in the table,
- * used as index by the frontend to skip previous results.
- */
-static void
-pd_cb (void *cls,
-       const char *order_id,
-       uint64_t row_id,
-       const json_t *contract_terms)
-{
-  struct ProcessContractClosure *pcc = cls;
-  json_t *entry;
-  json_t *amount;
-  json_t *timestamp;
-  json_t *instance;
-  json_t *summary;
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "/history's row_id: %llu\n",
-              (unsigned long long) row_id);
-  summary = NULL;
-  if (-1 == json_unpack ((json_t *) contract_terms,
-                         "{s:o, s:o, s:{s:o}, s?:o}",
-                         "amount", &amount,
-                         "timestamp", &timestamp,
-                         "merchant", "instance", &instance,
-                         "summary", &summary))
-  {
-    GNUNET_break (0);
-    pcc->failure = GNUNET_SYSERR;
-    return;
-  }
-
-  /* summary is optional, but we need something, so we use
-     the order ID if it is not given. */
-  if (NULL == summary)
-    summary = json_string (order_id);
-
-  if (NULL == (entry =
-                 json_pack ("{s:I, s:s, s:O, s:O, s:O, s:O}",
-                            "row_id", row_id,
-                            "order_id", order_id,
-                            "amount", amount,
-                            "timestamp", timestamp,
-                            "instance", instance,
-                            "summary", summary)))
-  {
-    GNUNET_break (0);
-    pcc->failure = GNUNET_SYSERR;
-    return;
-  }
-  if (0 !=
-      json_array_append_new (pcc->response,
-                             entry))
-  {
-    GNUNET_break (0);
-    pcc->failure = GNUNET_SYSERR;
-    json_decref (entry);
-    return;
-  }
-}
-
-
-/**
- * Manage a /history request. Query the db and returns transactions
- * younger than the date given as parameter
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_history (struct TMH_RequestHandler *rh,
-                    struct MHD_Connection *connection,
-                    void **connection_cls,
-                    const char *upload_data,
-                    size_t *upload_data_size,
-                    struct MerchantInstance *mi)
-{
-  #define LOG_INFO(...) GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__)
-  const char *str;
-  struct GNUNET_TIME_Absolute date;
-  json_t *response;
-  MHD_RESULT ret;
-  unsigned int ascending = GNUNET_NO;
-  unsigned long long seconds;
-  unsigned long long start = INT64_MAX;
-  long long delta = -20;
-  enum GNUNET_DB_QueryStatus qs;
-  struct ProcessContractClosure pcc;
-
-  LOG_INFO ("Serving /history\n");
-  response = json_array ();
-  str = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "date");
-  date = GNUNET_TIME_absolute_get ();
-  (void) GNUNET_TIME_round_abs (&date);
-  if (NULL != str)
-  {
-    if (1 != sscanf (str,
-                     "%llu",
-                     &seconds))
-    {
-      json_decref (response);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MALFORMED,
-                                         "date");
-    }
-    date.abs_value_us = seconds * 1000LL * 1000LL;
-    if (date.abs_value_us / 1000LL / 1000LL != seconds)
-    {
-      json_decref (response);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_HISTORY_TIMESTAMP_OVERFLOW,
-                                         "Timestamp overflowed");
-    }
-  }
-
-  /* Sanity check that we don't have some odd stale transaction running */
-  db->preflight (db->cls);
-
-  /* Here goes the cherry-picking logic */
-  str = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "order_id");
-  if (NULL != str)
-  {
-    pcc.response = response;
-    pcc.failure = GNUNET_NO;
-    qs = db->find_contract_terms_history (db->cls,
-                                          str,
-                                          &mi->pubkey,
-                                          &pd_cb,
-                                          &pcc);
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    if ( (0 > qs) ||
-         (GNUNET_SYSERR == pcc.failure) )
-    {
-      json_decref (response);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_HISTORY_DB_FETCH_ERROR,
-                                         "db error to get history");
-    }
-    ret = TALER_MHD_reply_json (connection,
-                                response,
-                                MHD_HTTP_OK);
-    json_decref (response);
-    return ret;
-  }
-
-  str = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "start");
-  if (NULL != str)
-  {
-    TALER_LOG_DEBUG ("'start' argument given ('%s')\n",
-                     str);
-    if (1 != sscanf (str,
-                     "%llu",
-                     &start))
-    {
-      json_decref (response);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MALFORMED,
-                                         "start");
-    }
-  }
-
-  str = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "delta");
-
-  if (NULL != str)
-  {
-    if (1 != sscanf (str,
-                     "%lld",
-                     &delta))
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MALFORMED,
-                                         "delta");
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Querying history back to %s, start: %llu, delta: %lld\n",
-              GNUNET_STRINGS_absolute_time_to_string (date),
-              start,
-              delta);
-
-  pcc.response = response;
-  pcc.failure = GNUNET_NO;
-
-  str = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "ordering");
-  if ( (NULL != str) &&
-       (0 == strcmp ("ascending",
-                     str)) )
-    ascending = GNUNET_YES;
-
-  qs = db->find_contract_terms_by_date_and_range (db->cls,
-                                                  date,
-                                                  &mi->pubkey,
-                                                  start,
-                                                  llabs (delta),
-                                                  (delta < 0) ? GNUNET_YES :
-                                                  GNUNET_NO,
-                                                  ascending,
-                                                  &pd_cb,
-                                                  &pcc);
-  if ( (0 > qs) ||
-       (GNUNET_SYSERR == pcc.failure) )
-  {
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    json_decref (response);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_HISTORY_DB_FETCH_ERROR,
-                                       "db error to get history");
-  }
-  ret = TALER_MHD_reply_json_pack (connection,
-                                   MHD_HTTP_OK,
-                                   "{ s:o }",
-                                   "history",
-                                   response /* consumes 'response' */);
-  LOG_INFO ("/history, http code: %d\n",
-            MHD_HTTP_OK);
-  return ret;
-}
-
-
-/* end of taler-merchant-httpd_history.c */
diff --git a/src/backend/taler-merchant-httpd_history.h 
b/src/backend/taler-merchant-httpd_history.h
deleted file mode 100644
index eac987d..0000000
--- a/src/backend/taler-merchant-httpd_history.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014, 2015, 2016 INRIA
-
-  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_history.c
- * @brief HTTP serving layer mainly intended to communicate with the frontend
- * @author Marcello Stanisci
- */
-
-#ifndef TALER_MERCHANT_HTTPD_HISTORY_H
-#define TALER_MERCHANT_HTTPD_HISTORY_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * Manage a /history request. Query the db and returns transactions
- * younger than the date given as parameter
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_history (struct TMH_RequestHandler *rh,
-                    struct MHD_Connection *connection,
-                    void **connection_cls,
-                    const char *upload_data,
-                    size_t *upload_data_size,
-                    struct MerchantInstance *mi);
-
-/* end of taler-merchant-httpd_history.c */
-#endif
diff --git a/src/backend/taler-merchant-httpd_mhd.c 
b/src/backend/taler-merchant-httpd_mhd.c
index 0860860..698c2ac 100644
--- a/src/backend/taler-merchant-httpd_mhd.c
+++ b/src/backend/taler-merchant-httpd_mhd.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2019 Taler Systems SA
+  Copyright (C) 2014-2020 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
@@ -33,27 +33,15 @@
  *
  * @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
- * @param mi merchant backend instance, could be NULL in this specific case!
+ * @param[in,out] hc handler context (can be updated)
  * @return MHD result code
  */
 MHD_RESULT
-TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh,
+TMH_MHD_handler_static_response (const struct TMH_RequestHandler *rh,
                                  struct MHD_Connection *connection,
-                                 void **connection_cls,
-                                 const char *upload_data,
-                                 size_t *upload_data_size,
-                                 struct MerchantInstance *instance)
+                                 struct TMH_HandlerContext *hc)
 {
-  (void) instance;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  (void) instance;
-  if (0 == rh->data_size)
-    rh->data_size = strlen ((const char *) rh->data);
+  (void) hc;
   return TALER_MHD_reply_static (connection,
                                  rh->response_code,
                                  rh->mime_type,
@@ -68,24 +56,16 @@ TMH_MHD_handler_static_response (struct TMH_RequestHandler 
*rh,
  *
  * @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
- * @param mi merchant backend instance, never NULL (but unused)
+ * @param[in,out] hc handler context (can be updated)
  * @return MHD result code
  */
 MHD_RESULT
-TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh,
+TMH_MHD_handler_agpl_redirect (const struct TMH_RequestHandler *rh,
                                struct MHD_Connection *connection,
-                               void **connection_cls,
-                               const char *upload_data,
-                               size_t *upload_data_size,
-                               struct MerchantInstance *mi)
+                               struct TMH_HandlerContext *hc)
 {
-  (void) mi;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) rh;
+  (void) hc;
   return TALER_MHD_reply_agpl (connection,
                                "http://www.git.taler.net/?p=merchant.git";);
 }
diff --git a/src/backend/taler-merchant-httpd_mhd.h 
b/src/backend/taler-merchant-httpd_mhd.h
index 8fc78a2..cbf83ad 100644
--- a/src/backend/taler-merchant-httpd_mhd.h
+++ b/src/backend/taler-merchant-httpd_mhd.h
@@ -34,19 +34,13 @@
  *
  * @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
- * @param mi merchant backend instance, NULL is allowed in this case!
+ * @param[in,out] hc handler context (can be updated)
  * @return MHD result code
  */
 MHD_RESULT
-TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh,
+TMH_MHD_handler_static_response (const struct TMH_RequestHandler *rh,
                                  struct MHD_Connection *connection,
-                                 void **connection_cls,
-                                 const char *upload_data,
-                                 size_t *upload_data_size,
-                                 struct MerchantInstance *mi);
+                                 struct TMH_HandlerContext *hc);
 
 
 /**
@@ -55,19 +49,13 @@ TMH_MHD_handler_static_response (struct TMH_RequestHandler 
*rh,
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc handler context (can be updated)
  * @return MHD result code
  */
 MHD_RESULT
-TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh,
+TMH_MHD_handler_agpl_redirect (const struct TMH_RequestHandler *rh,
                                struct MHD_Connection *connection,
-                               void **connection_cls,
-                               const char *upload_data,
-                               size_t *upload_data_size,
-                               struct MerchantInstance *mi);
+                               struct TMH_HandlerContext *hc);
 
 
 /**
@@ -111,7 +99,7 @@ TMH_MHD_handler_send_json_pack_error (struct 
TMH_RequestHandler *rh,
                                       void **connection_cls,
                                       const char *upload_data,
                                       size_t *upload_data_size,
-                                      struct MerchantInstance *mi);
+                                      struct TMH_MerchantInstance *mi);
 
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_order.c 
b/src/backend/taler-merchant-httpd_order.c
deleted file mode 100644
index 317e451..0000000
--- a/src/backend/taler-merchant-httpd_order.c
+++ /dev/null
@@ -1,747 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014, 2015, 2016, 2018, 2020 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_order.c
- * @brief HTTP serving layer mainly intended to communicate
- * with the frontend
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_auditors.h"
-#include "taler-merchant-httpd_exchanges.h"
-
-
-/**
- * How often do we retry the simple INSERT database transaction?
- */
-#define MAX_RETRIES 3
-
-/**
- * What is the label under which we find/place the merchant's
- * jurisdiction in the locations list by default?
- */
-#define STANDARD_LABEL_MERCHANT_JURISDICTION "_mj"
-
-/**
- * What is the label under which we find/place the merchant's
- * address in the locations list by default?
- */
-#define STANDARD_LABEL_MERCHANT_ADDRESS "_ma"
-
-
-/**
- * Check that the given JSON array of products is well-formed.
- *
- * @param products JSON array to check
- * @return #GNUNET_OK if all is fine
- */
-static int
-check_products (json_t *products)
-{
-  size_t index;
-  json_t *value;
-
-  if (! json_is_array (products))
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  json_array_foreach (products, index, value) {
-    const char *description;
-    const char *error_name;
-    unsigned int error_line;
-    int res;
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_string ("description", &description),
-      /* FIXME: there are other fields in the product specification
-         that are currently not labeled as optional. Maybe check
-        those as well, or make them truly optional. */
-      GNUNET_JSON_spec_end ()
-    };
-
-    /* extract fields we need to sign separately */
-    res = GNUNET_JSON_parse (value,
-                             spec,
-                             &error_name,
-                             &error_line);
-    if (GNUNET_OK != res)
-    {
-      GNUNET_break (0);
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Product description parsing failed at #%u: %s:%u\n",
-                  (unsigned int) index,
-                  error_name,
-                  error_line);
-      return GNUNET_SYSERR;
-    }
-    GNUNET_JSON_parse_free (spec);
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Information we keep for individual calls
- * to requests that parse JSON, but keep no other state.
- */
-struct TMH_JsonParseContext
-{
-
-  /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Placeholder for #TALER_MHD_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;
-
-  TALER_MHD_parse_post_cleanup_callback (jpc->json_parse_context);
-  GNUNET_free (jpc);
-}
-
-
-/**
- * Generate the base URL for the given merchant instance.
- *
- * @param connection the MHD connection
- * @param instance_id the merchant instance ID
- * @returns the merchant instance's base URL
- */
-static char *
-make_merchant_base_url (struct MHD_Connection *connection,
-                        const char *instance_id)
-{
-  const char *host;
-  const char *forwarded_host;
-  const char *uri_path;
-  struct GNUNET_Buffer buf = { 0 };
-
-  if (GNUNET_YES == TALER_mhd_is_https (connection))
-    GNUNET_buffer_write_str (&buf, "https://";);
-  else
-    GNUNET_buffer_write_str (&buf, "http://";);
-
-
-  host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Host");
-  forwarded_host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
-                                                "X-Forwarded-Host");
-
-  if (NULL != forwarded_host)
-  {
-    GNUNET_buffer_write_str (&buf, forwarded_host);
-  }
-  else
-  {
-    GNUNET_assert (NULL != host);
-    GNUNET_buffer_write_str (&buf, host);
-  }
-
-  uri_path = MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
-                                          "X-Forwarded-Prefix");
-  if (NULL != uri_path)
-  {
-    /* Currently the merchant backend is only supported at the root of the 
path,
-       this might change in the future.  */
-    GNUNET_assert (0);
-  }
-
-  GNUNET_buffer_write_path (&buf, "public");
-
-  if (0 != strcmp (instance_id, "default"))
-  {
-    GNUNET_buffer_write_path (&buf, "/instances/");
-    GNUNET_buffer_write_str (&buf, instance_id);
-  }
-  GNUNET_buffer_write_path (&buf, "");
-
-  return GNUNET_buffer_reap_str (&buf);
-}
-
-
-/**
- * Transform an order into a proposal and store it in the
- * database. Write the resulting proposal or an error message
- * of a MHD connection.
- *
- * @param connection connection to write the result or error to
- * @param root root of the request
- * @param order[in] order to process (can be modified)
- * @return MHD result code
- */
-static MHD_RESULT
-proposal_put (struct MHD_Connection *connection,
-              json_t *root,
-              json_t *order,
-              const struct MerchantInstance *mi)
-{
-  struct TALER_Amount total;
-  const char *order_id;
-  const char *summary;
-  const char *fulfillment_url;
-  json_t *products;
-  json_t *merchant;
-  struct GNUNET_TIME_Absolute timestamp;
-  struct GNUNET_TIME_Absolute refund_deadline;
-  struct GNUNET_TIME_Absolute wire_transfer_deadline;
-  struct GNUNET_TIME_Absolute pay_deadline;
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount ("amount", &total),
-    GNUNET_JSON_spec_string ("order_id", &order_id),
-    GNUNET_JSON_spec_string ("summary", &summary),
-    GNUNET_JSON_spec_string ("fulfillment_url",
-                             &fulfillment_url),
-    /**
-     * 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),
-    GNUNET_JSON_spec_absolute_time ("refund_deadline",
-                                    &refund_deadline),
-    GNUNET_JSON_spec_absolute_time ("pay_deadline",
-                                    &pay_deadline),
-    GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
-                                    &wire_transfer_deadline),
-    GNUNET_JSON_spec_end ()
-  };
-  enum GNUNET_DB_QueryStatus qs;
-  struct WireMethod *wm;
-
-  /* Add order_id if it doesn't exist. */
-  if (NULL ==
-      json_string_value (json_object_get (order,
-                                          "order_id")))
-  {
-    char buf[256];
-    time_t timer;
-    struct tm*tm_info;
-    size_t off;
-    uint64_t rand;
-    char *last;
-
-    time (&timer);
-    tm_info = localtime (&timer);
-    if (NULL == tm_info)
-    {
-      return TALER_MHD_reply_with_error
-               (connection,
-               MHD_HTTP_INTERNAL_SERVER_ERROR,
-               TALER_EC_PROPOSAL_NO_LOCALTIME,
-               "failed to determine local time");
-    }
-    off = strftime (buf,
-                    sizeof (buf),
-                    "%Y.%j",
-                    tm_info);
-    buf[off++] = '-';
-    rand = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
-                                     UINT64_MAX);
-    last = GNUNET_STRINGS_data_to_string (&rand,
-                                          sizeof (uint64_t),
-                                          &buf[off],
-                                          sizeof (buf) - off);
-    *last = '\0';
-    json_object_set_new (order,
-                         "order_id",
-                         json_string (buf));
-  }
-
-  /* Add timestamp if it doesn't exist */
-  if (NULL == json_object_get (order,
-                               "timestamp"))
-  {
-    struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
-
-    (void) GNUNET_TIME_round_abs (&now);
-    json_object_set_new (order,
-                         "timestamp",
-                         GNUNET_JSON_from_time_abs (now));
-  }
-
-  /* If no refund_deadline given, set one as zero.  */
-  if (NULL == json_object_get (order,
-                               "refund_deadline"))
-  {
-    struct GNUNET_TIME_Absolute zero = { 0 };
-
-    json_object_set_new (order,
-                         "refund_deadline",
-                         GNUNET_JSON_from_time_abs (zero));
-  }
-
-  if (NULL == json_object_get (order,
-                               "pay_deadline"))
-  {
-    struct GNUNET_TIME_Absolute t;
-
-    t = GNUNET_TIME_relative_to_absolute (default_pay_deadline);
-    (void) GNUNET_TIME_round_abs (&t);
-    json_object_set_new (order,
-                         "pay_deadline",
-                         GNUNET_JSON_from_time_abs (t));
-  }
-
-  if (NULL == json_object_get (order,
-                               "wire_transfer_deadline"))
-  {
-    struct GNUNET_TIME_Absolute t;
-    t = GNUNET_TIME_relative_to_absolute (default_wire_transfer_delay);
-    (void) GNUNET_TIME_round_abs (&t);
-    json_object_set_new (order,
-                         "wire_transfer_deadline",
-                         GNUNET_JSON_from_time_abs (t));
-  }
-
-  if (NULL == json_object_get (order,
-                               "max_wire_fee"))
-  {
-    json_object_set_new (order,
-                         "max_wire_fee",
-                         TALER_JSON_from_amount
-                           (&default_max_wire_fee));
-  }
-
-  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,
-      "wire_fee_amortization",
-      json_integer
-        ((json_int_t) default_wire_fee_amortization));
-  }
-
-  if (NULL == json_object_get (order,
-                               "merchant_base_url"))
-  {
-    char *url;
-
-    url = make_merchant_base_url (connection, mi->id);
-    json_object_set_new (order,
-                         "merchant_base_url",
-                         json_string (url));
-    GNUNET_free (url);
-  }
-
-  if (NULL == json_object_get (order,
-                               "products"))
-  {
-    json_object_set_new (order,
-                         "products",
-                         json_array ());
-  }
-
-  /* Fill in merchant information if necessary */
-  if (NULL == json_object_get (order, "merchant"))
-  {
-    const char *mj = NULL;
-    const char *ma = NULL;
-    json_t *locations;
-    char *label;
-    json_t *jmerchant;
-
-    jmerchant = json_object ();
-    json_object_set_new (jmerchant,
-                         "name",
-                         json_string (mi->name));
-    json_object_set_new (jmerchant,
-                         "instance",
-                         json_string (mi->id));
-    locations = json_object_get (order,
-                                 "locations");
-    if (NULL != locations)
-    {
-      json_t *loca;
-      json_t *locj;
-
-      /* Handle merchant address */
-      GNUNET_assert (0 < GNUNET_asprintf (&label,
-                                          "%s-address",
-                                          mi->id));
-      loca = json_object_get (default_locations,
-                              label);
-      if (NULL != loca)
-      {
-        loca = json_deep_copy (loca);
-        ma = STANDARD_LABEL_MERCHANT_ADDRESS;
-        json_object_set_new (locations,
-                             ma,
-                             loca);
-        json_object_set_new (jmerchant,
-                             "address",
-                             json_string (ma));
-      }
-      GNUNET_free (label);
-
-      /* Handle merchant jurisdiction */
-      GNUNET_assert (0 < GNUNET_asprintf (&label,
-                                          "%s-jurisdiction",
-                                          mi->id));
-      locj = json_object_get (default_locations,
-                              label);
-      if (NULL != locj)
-      {
-        if ( (NULL != loca) &&
-             (1 == json_equal (locj,
-                               loca)) )
-        {
-          /* addresses equal, re-use */
-          mj = ma;
-        }
-        else
-        {
-          locj = json_deep_copy (locj);
-          mj = STANDARD_LABEL_MERCHANT_JURISDICTION;
-          json_object_set_new (locations,
-                               mj,
-                               locj);
-        }
-        json_object_set_new (merchant,
-                             "jurisdiction",
-                             json_string (mj));
-      }
-      GNUNET_free (label);
-    } /* have locations */
-    json_object_set_new (order,
-                         "merchant",
-                         jmerchant);
-  } /* needed to synthesize merchant info */
-
-  /* extract fields we need to sign separately */
-  {
-    enum GNUNET_GenericReturnValue res;
-
-    res = TALER_MHD_parse_json_data (connection,
-                                     order,
-                                     spec);
-    /* json is malformed */
-    if (GNUNET_NO == res)
-    {
-      return MHD_YES;
-    }
-    /* other internal errors might have occurred */
-    if (GNUNET_SYSERR == res)
-    {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_PROPOSAL_ORDER_PARSE_ERROR,
-                                         "Impossible to parse the order");
-    }
-  }
-  if (0 !=
-      strcasecmp (total.currency,
-                  TMH_currency))
-  {
-    GNUNET_break_op (0);
-    return TALER_MHD_reply_with_error
-             (connection,
-             MHD_HTTP_BAD_REQUEST,
-             TALER_EC_PROPOSAL_ORDER_BAD_CURRENCY,
-             "Total amount must be in currency supported by backend");
-  }
-
-  if (wire_transfer_deadline.abs_value_us <
-      refund_deadline.abs_value_us)
-  {
-    GNUNET_JSON_parse_free (spec);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "invariant failed: wire_transfer_deadline >= 
refund_deadline\n");
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "wire_transfer_deadline: %s\n",
-                GNUNET_STRINGS_absolute_time_to_string (
-                  wire_transfer_deadline));
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "refund_deadline: %s\n",
-                GNUNET_STRINGS_absolute_time_to_string (refund_deadline));
-    return TALER_MHD_reply_with_error
-             (connection,
-             MHD_HTTP_BAD_REQUEST,
-             TALER_EC_PARAMETER_MALFORMED,
-             "order:wire_transfer_deadline;order:refund_deadline");
-  }
-
-
-  /* check contract is well-formed */
-  if (GNUNET_OK != check_products (products))
-  {
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MALFORMED,
-                                       "order:products");
-  }
-
-  /* add fields to the contract that the backend should provide */
-  json_object_set (order,
-                   "exchanges",
-                   TMH_trusted_exchanges);
-
-  json_object_set (order,
-                   "auditors",
-                   j_auditors);
-  {
-    const char *target;
-
-    target = MHD_lookup_connection_value (connection,
-                                          MHD_GET_ARGUMENT_KIND,
-                                          "payment_target");
-    wm = mi->wm_head;
-    if (NULL != target)
-    {
-      while ( (NULL != wm) &&
-              (GNUNET_YES == wm->active) &&
-              (0 != strcasecmp (target,
-                                wm->wire_method) ) )
-        wm = wm->next;
-    }
-    if (GNUNET_YES != wm->active)
-      wm = NULL;
-  }
-
-  if (NULL == wm)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "No wire method available for instance '%s'\n",
-                mi->id);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       
TALER_EC_PROPOSAL_INSTANCE_CONFIGURATION_LACKS_WIRE,
-                                       "No wire method configured for 
instance");
-  }
-  json_object_set_new (order,
-                       "h_wire",
-                       GNUNET_JSON_from_data_auto (&wm->h_wire));
-  json_object_set_new (order,
-                       "wire_method",
-                       json_string (wm->wire_method));
-  json_object_set_new (order,
-                       "merchant_pub",
-                       GNUNET_JSON_from_data_auto (&mi->pubkey));
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Inserting order '%s' for instance '%s'\n",
-              order_id,
-              mi->id);
-
-  for (unsigned int i = 0; i<MAX_RETRIES; i++)
-  {
-    db->preflight (db->cls);
-    qs = db->insert_order (db->cls,
-                           order_id,
-                           &mi->pubkey,
-                           timestamp,
-                           order);
-    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-      break;
-  }
-  if (0 > qs)
-  {
-    /* Special report if retries insufficient */
-    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-    {
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_PROPOSAL_STORE_DB_ERROR_SOFT,
-                                         "db error: could not check for 
existing order"
-                                         " due to repeated soft transaction 
failure");
-    }
-
-    {
-      /* Hard error could be constraint violation,
-         check if order already exists */
-      json_t *contract_terms = NULL;
-
-      db->preflight (db->cls);
-      qs = db->find_order (db->cls,
-                           &contract_terms,
-                           order_id,
-                           &mi->pubkey);
-      if (0 < qs)
-      {
-        /* Yep, indeed uniqueness constraint violation */
-        int rv;
-        char *msg;
-
-        GNUNET_JSON_parse_free (spec);
-        GNUNET_asprintf (&msg,
-                         "order ID `%s' already exists",
-                         order_id);
-        {
-          /* Log plenty of details for the admin */
-          char *js;
-
-          js = json_dumps (contract_terms,
-                           JSON_COMPACT);
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      _ ("Order ID `%s' already exists with proposal `%s'\n"),
-                      order_id,
-                      js);
-          free (js);
-        }
-        json_decref (contract_terms);
-
-        /* contract_terms may be private, only expose
-         * duplicate order_id to the network */
-        rv = TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST, /* or conflict? 
*/
-                                         
TALER_EC_PROPOSAL_STORE_DB_ERROR_ALREADY_EXISTS,
-                                         msg);
-        GNUNET_free (msg);
-        return rv;
-      }
-    }
-
-    /* Other hard transaction error (disk full, etc.) */
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error
-             (connection,
-             MHD_HTTP_INTERNAL_SERVER_ERROR,
-             TALER_EC_PROPOSAL_STORE_DB_ERROR_HARD,
-             "db error: could not store this proposal's data into db");
-  }
-
-  /* DB transaction succeeded, generate positive response */
-  {
-    MHD_RESULT ret;
-
-    ret = TALER_MHD_reply_json_pack (connection,
-                                     MHD_HTTP_OK,
-                                     "{s:s}",
-                                     "order_id",
-                                     order_id);
-    GNUNET_JSON_parse_free (spec);
-    return ret;
-  }
-}
-
-
-/**
- * Generate a proposal, given its order. In practical terms,
- * it adds the fields  'exchanges', 'merchant_pub', and 'h_wire'
- * to the order gotten from the frontend. Finally, it signs this
- * data, and returns it to the frontend.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-int
-MH_handler_order_post (struct TMH_RequestHandler *rh,
-                       struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size,
-                       struct MerchantInstance *mi)
-{
-  struct TMH_JsonParseContext *ctx;
-  json_t *root;
-  json_t *order;
-
-  if (NULL == *connection_cls)
-  {
-    ctx = GNUNET_new (struct TMH_JsonParseContext);
-    ctx->hc.cc = &json_parse_cleanup;
-    *connection_cls = ctx;
-  }
-  else
-  {
-    ctx = *connection_cls;
-  }
-
-  {
-    int res;
-
-    res = TALER_MHD_parse_post_json (connection,
-                                     &ctx->json_parse_context,
-                                     upload_data,
-                                     upload_data_size,
-                                     &root);
-
-    if (GNUNET_SYSERR == res)
-      return MHD_NO;
-
-    /* A error response was already generated */
-    if ( (GNUNET_NO == res) ||
-         /* or, need more data to accomplish parsing */
-         (NULL == root) )
-      return MHD_YES;
-  }
-  order = json_object_get (root,
-                           "order");
-  {
-    MHD_RESULT ret;
-
-    if (NULL == order)
-    {
-      ret = TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_BAD_REQUEST,
-                                        TALER_EC_PARAMETER_MISSING,
-                                        "order");
-    }
-    else
-    {
-      ret = proposal_put (connection,
-                          root,
-                          order,
-                          mi);
-    }
-    json_decref (root);
-    return ret;
-  }
-}
-
-
-/* end of taler-merchant-httpd_order.c */
diff --git a/src/backend/taler-merchant-httpd_order.h 
b/src/backend/taler-merchant-httpd_order.h
deleted file mode 100644
index cf43d1b..0000000
--- a/src/backend/taler-merchant-httpd_order.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014, 2015, 2019 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_order.h
- * @brief headers for /order handler
- * @author Marcello Stanisci
- */
-#ifndef TALER_MERCHANT_HTTPD_ORDER_H
-#define TALER_MERCHANT_HTTPD_ORDER_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-/**
- * Generate a proposal, given its order. In practical terms, it adds the
- * fields  'exchanges', 'merchant_pub', and 'H_wire' to the order gotten
- * from the frontend. Finally, it signs this data, and returns it to the
- * frontend.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_order_post (struct TMH_RequestHandler *rh,
-                       struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size,
-                       struct MerchantInstance *mi);
-
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_poll-payment.c 
b/src/backend/taler-merchant-httpd_poll-payment.c
deleted file mode 100644
index 6ca4fcc..0000000
--- a/src/backend/taler-merchant-httpd_poll-payment.c
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2017, 2019 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_poll-payment.c
- * @brief implementation of /public/poll-payment handler
- * @author Florian Dold
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <string.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_exchanges.h"
-#include "taler-merchant-httpd_poll-payment.h"
-
-/**
- * Maximum number of retries for database operations.
- */
-#define MAX_RETRIES 5
-
-
-/**
- * Data structure we keep for a check payment request.
- */
-struct PollPaymentRequestContext
-{
-  /**
-   * Must be first for #handle_mhd_completion_callback.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Entry in the #resume_timeout_heap for this check payment, if we are
-   * suspended.
-   */
-  struct TMH_SuspendedConnection sc;
-
-  /**
-   * Which merchant instance is this for?
-   */
-  struct MerchantInstance *mi;
-
-  /**
-   * URL where the final contract can be found for this payment.
-   */
-  char *final_contract_url;
-
-  /**
-   * order ID for the payment
-   */
-  const char *order_id;
-
-  /**
-   * Where to get the contract
-   */
-  const char *contract_url;
-
-  /**
-   * fulfillment URL of the contract (valid as long as
-   * @e contract_terms is valid).
-   */
-  const char *fulfillment_url;
-
-  /**
-   * session of the client
-   */
-  const char *session_id;
-
-  /**
-   * Contract terms of the payment we are checking. NULL when they
-   * are not (yet) known.
-   */
-  json_t *contract_terms;
-
-  /**
-   * Hash of @e contract_terms, set only once @e contract_terms
-   * is available.
-   */
-  struct GNUNET_HashCode h_contract_terms;
-
-  /**
-   * Total refunds granted for this payment. Only initialized
-   * if @e refunded is set to #GNUNET_YES.
-   */
-  struct TALER_Amount refund_amount;
-
-  /**
-   * Minimum refund amount the client would like to poll for.
-   * Only initialized if
-   * @e awaiting_refund is set to #GNUNET_YES.
-   */
-  struct TALER_Amount min_refund;
-
-  /**
-   * Set to #GNUNET_YES if this payment has been refunded and
-   * @e refund_amount is initialized.
-   */
-  int refunded;
-
-  /**
-   * Set to #GNUNET_YES if this client is waiting for a refund.
-   */
-  int awaiting_refund;
-
-  /**
-   * Initially #GNUNET_SYSERR. If we queued a response, set to the
-   * result code (i.e. #MHD_YES or #MHD_NO). FIXME: fix type!
-   */
-  int ret;
-
-};
-
-
-/**
- * Clean up the session state for a check payment request.
- *
- * @param hc must be a `struct PollPaymentRequestContext *`
- */
-static void
-pprc_cleanup (struct TM_HandlerContext *hc)
-{
-  struct PollPaymentRequestContext *pprc
-    = (struct PollPaymentRequestContext *) hc;
-
-  if (NULL != pprc->contract_terms)
-    json_decref (pprc->contract_terms);
-  GNUNET_free_non_null (pprc->final_contract_url);
-  GNUNET_free (pprc);
-}
-
-
-/**
- * Function called with information about a refund.
- * It is responsible for summing up the refund amount.
- *
- * @param cls closure
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
- */
-static void
-process_refunds_cb (void *cls,
-                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                    const char *exchange_url,
-                    uint64_t rtransaction_id,
-                    const char *reason,
-                    const struct TALER_Amount *refund_amount,
-                    const struct TALER_Amount *refund_fee)
-{
-  struct PollPaymentRequestContext *pprc = cls;
-
-  if (pprc->refunded)
-  {
-    GNUNET_assert (0 <=
-                   TALER_amount_add (&pprc->refund_amount,
-                                     &pprc->refund_amount,
-                                     refund_amount));
-    return;
-  }
-  pprc->refund_amount = *refund_amount;
-  pprc->refunded = GNUNET_YES;
-}
-
-
-/**
- * Suspend this @a pprc until the trigger is satisfied.
- *
- * @param ppr
- */
-static void
-suspend_pprc (struct PollPaymentRequestContext *pprc)
-{
-  TMH_compute_pay_key (pprc->order_id,
-                       &pprc->mi->pubkey,
-                       &pprc->sc.key);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Suspending /poll-payment on key %s\n",
-              GNUNET_h2s (&pprc->sc.key));
-  TMH_long_poll_suspend (&pprc->sc,
-                         (pprc->awaiting_refund)
-                         ? &pprc->min_refund
-                         : NULL);
-}
-
-
-/**
- * The client did not yet pay, send it the payment request.
- *
- * @param pprc check pay request context
- * @return #MHD_YES on success
- */
-static MHD_RESULT
-send_pay_request (struct PollPaymentRequestContext *pprc)
-{
-  MHD_RESULT ret;
-  char *already_paid_order_id = NULL;
-  char *taler_pay_uri;
-  struct GNUNET_TIME_Relative remaining;
-
-  remaining = GNUNET_TIME_absolute_get_remaining (pprc->sc.long_poll_timeout);
-  if (0 != remaining.rel_value_us)
-  {
-    /* long polling: do not queue a response, suspend connection instead */
-    suspend_pprc (pprc);
-    return MHD_YES;
-  }
-
-  /* Check if resource_id has been paid for in the same session
-   * with another order_id.
-   */
-  if ( (NULL != pprc->session_id) &&
-       (NULL != pprc->fulfillment_url) )
-  {
-    enum GNUNET_DB_QueryStatus qs;
-
-    qs = db->find_session_info (db->cls,
-                                &already_paid_order_id,
-                                pprc->session_id,
-                                pprc->fulfillment_url,
-                                &pprc->mi->pubkey);
-    if (qs < 0)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (pprc->sc.con,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
-                                         "db error fetching pay session info");
-    }
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Sending payment request in /poll-payment\n");
-  taler_pay_uri = TMH_make_taler_pay_uri (pprc->sc.con,
-                                          pprc->order_id,
-                                          pprc->session_id,
-                                          pprc->mi->id);
-  ret = TALER_MHD_reply_json_pack (pprc->sc.con,
-                                   MHD_HTTP_OK,
-                                   "{s:s, s:s, s:b, s:s?}",
-                                   "taler_pay_uri", taler_pay_uri,
-                                   "contract_url", pprc->final_contract_url,
-                                   "paid", 0,
-                                   "already_paid_order_id",
-                                   already_paid_order_id);
-  GNUNET_free (taler_pay_uri);
-  GNUNET_free_non_null (already_paid_order_id);
-  return ret;
-}
-
-
-/**
- * Manages a /public/poll-payment call, checking the status
- * of a payment and, if necessary, constructing the URL
- * for a payment redirect URL.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_poll_payment (struct TMH_RequestHandler *rh,
-                         struct MHD_Connection *connection,
-                         void **connection_cls,
-                         const char *upload_data,
-                         size_t *upload_data_size,
-                         struct MerchantInstance *mi)
-{
-  struct PollPaymentRequestContext *pprc = *connection_cls;
-  enum GNUNET_DB_QueryStatus qs;
-  MHD_RESULT ret;
-
-  if (NULL == pprc)
-  {
-    /* First time here, parse request and check order is known */
-    const char *long_poll_timeout_s;
-    const char *cts;
-    const char *min_refund;
-
-    pprc = GNUNET_new (struct PollPaymentRequestContext);
-    pprc->hc.cc = &pprc_cleanup;
-    pprc->ret = GNUNET_SYSERR;
-    pprc->sc.con = connection;
-    pprc->mi = mi;
-    *connection_cls = pprc;
-
-    pprc->order_id = MHD_lookup_connection_value (connection,
-                                                  MHD_GET_ARGUMENT_KIND,
-                                                  "order_id");
-    if (NULL == pprc->order_id)
-    {
-      /* order_id is required but missing */
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "order_id required");
-    }
-    cts = MHD_lookup_connection_value (connection,
-                                       MHD_GET_ARGUMENT_KIND,
-                                       "h_contract");
-    if (NULL == cts)
-    {
-      /* h_contract required but missing */
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "h_contract required");
-    }
-    if (GNUNET_OK !=
-        GNUNET_CRYPTO_hash_from_string (cts,
-                                        &pprc->h_contract_terms))
-    {
-      /* cts has wrong encoding */
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MALFORMED,
-                                         "h_contract malformed");
-    }
-    long_poll_timeout_s = MHD_lookup_connection_value (connection,
-                                                       MHD_GET_ARGUMENT_KIND,
-                                                       "timeout");
-    if (NULL != long_poll_timeout_s)
-    {
-      unsigned int timeout;
-
-      if (1 != sscanf (long_poll_timeout_s,
-                       "%u",
-                       &timeout))
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           TALER_EC_PARAMETER_MALFORMED,
-                                           "timeout must be non-negative 
number");
-      }
-      pprc->sc.long_poll_timeout
-        = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
-                                              GNUNET_TIME_UNIT_SECONDS,
-                                              timeout));
-    }
-    else
-    {
-      pprc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
-    }
-
-    min_refund = MHD_lookup_connection_value (connection,
-                                              MHD_GET_ARGUMENT_KIND,
-                                              "refund");
-    if (NULL != min_refund)
-    {
-      if ( (GNUNET_OK !=
-            TALER_string_to_amount (min_refund,
-                                    &pprc->min_refund)) ||
-           (0 != strcasecmp (pprc->min_refund.currency,
-                             TMH_currency) ) )
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           TALER_EC_PARAMETER_MALFORMED,
-                                           "invalid amount given for refund 
argument");
-      }
-      pprc->awaiting_refund = GNUNET_YES;
-    }
-
-    pprc->contract_url = MHD_lookup_connection_value (connection,
-                                                      MHD_GET_ARGUMENT_KIND,
-                                                      "contract_url");
-    if (NULL == pprc->contract_url)
-    {
-      pprc->final_contract_url = TALER_url_absolute_mhd (connection,
-                                                         "/public/proposal",
-                                                         "instance", mi->id,
-                                                         "order_id",
-                                                         pprc->order_id,
-                                                         NULL);
-      GNUNET_assert (NULL != pprc->final_contract_url);
-    }
-    else
-    {
-      pprc->final_contract_url = GNUNET_strdup (pprc->contract_url);
-    }
-    pprc->session_id = MHD_lookup_connection_value (connection,
-                                                    MHD_GET_ARGUMENT_KIND,
-                                                    "session_id");
-
-    /* obtain contract terms, indirectly checking that the client's contract
-       terms hash is actually valid and known. */
-    db->preflight (db->cls);
-    qs = db->find_contract_terms_from_hash (db->cls,
-                                            &pprc->contract_terms,
-                                            &pprc->h_contract_terms,
-                                            &mi->pubkey);
-    if (0 > qs)
-    {
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                         "Merchant database error");
-    }
-    if (0 == qs)
-    {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         
TALER_EC_POLL_PAYMENT_CONTRACT_NOT_FOUND,
-                                         "Given order_id doesn't map to any 
proposal");
-    }
-    GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
-
-    {
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_string ("fulfillment_url",
-                                 &pprc->fulfillment_url),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (pprc->contract_terms,
-                             spec,
-                             NULL, NULL))
-      {
-        GNUNET_break (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           
TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR,
-                                           "Merchant database error (contract 
terms corrupted)");
-      }
-    }
-  } /* end of first-time initialization / sanity checks */
-
-  db->preflight (db->cls);
-
-  /* Check if the order has been paid for. */
-  if (NULL != pprc->session_id)
-  {
-    /* Check if paid within a session. */
-    char *already_paid_order_id = NULL;
-
-    qs = db->find_session_info (db->cls,
-                                &already_paid_order_id,
-                                pprc->session_id,
-                                pprc->fulfillment_url,
-                                &mi->pubkey);
-    if (qs < 0)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR,
-                                         "db error fetching pay session info");
-    }
-    else if (0 == qs)
-    {
-      ret = send_pay_request (pprc);
-      GNUNET_free_non_null (already_paid_order_id);
-      return ret;
-    }
-    GNUNET_break (1 == qs);
-    GNUNET_break (0 == strcmp (pprc->order_id,
-                               already_paid_order_id));
-    GNUNET_free_non_null (already_paid_order_id);
-  }
-  else
-  {
-    /* Check if paid regardless of session. */
-    json_t *xcontract_terms = NULL;
-
-    qs = db->find_paid_contract_terms_from_hash (db->cls,
-                                                 &xcontract_terms,
-                                                 &pprc->h_contract_terms,
-                                                 &mi->pubkey);
-    if (0 > qs)
-    {
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                         "Merchant database error");
-    }
-    if (0 == qs)
-    {
-      return send_pay_request (pprc);
-    }
-    GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
-    GNUNET_assert (NULL != xcontract_terms);
-    json_decref (xcontract_terms);
-  }
-
-  /* Accumulate refunds, if any. */
-  for (unsigned int i = 0; i<MAX_RETRIES; i++)
-  {
-    pprc->refunded = GNUNET_NO;
-    qs = db->get_refunds_from_contract_terms_hash (db->cls,
-                                                   &mi->pubkey,
-                                                   &pprc->h_contract_terms,
-                                                   &process_refunds_cb,
-                                                   pprc);
-    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-      break;
-  }
-  if (0 > qs)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Database hard error on refunds_from_contract_terms_hash 
lookup: %s\n",
-                GNUNET_h2s (&pprc->h_contract_terms));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                       "Merchant database error");
-  }
-  if ( (pprc->awaiting_refund) &&
-       ( (! pprc->refunded) ||
-         (1 != TALER_amount_cmp (&pprc->refund_amount,
-                                 &pprc->min_refund)) ) )
-  {
-    /* Client is waiting for a refund larger than what we have, suspend
-       until timeout */
-    struct GNUNET_TIME_Relative remaining;
-
-    remaining = GNUNET_TIME_absolute_get_remaining 
(pprc->sc.long_poll_timeout);
-    if (0 != remaining.rel_value_us)
-    {
-      /* yes, indeed suspend */
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "Awaiting refund exceeding %s\n",
-                  TALER_amount2s (&pprc->min_refund));
-      suspend_pprc (pprc);
-      return MHD_YES;
-    }
-  }
-
-  if (pprc->refunded)
-    return TALER_MHD_reply_json_pack (connection,
-                                      MHD_HTTP_OK,
-                                      "{s:b, s:b, s:o}",
-                                      "paid", 1,
-                                      "refunded", pprc->refunded,
-                                      "refund_amount",
-                                      TALER_JSON_from_amount (
-                                        &pprc->refund_amount));
-  return TALER_MHD_reply_json_pack (connection,
-                                    MHD_HTTP_OK,
-                                    "{s:b, s:b }",
-                                    "paid", 1,
-                                    "refunded", 0);
-}
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
new file mode 100644
index 0000000..8c09213
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
@@ -0,0 +1,1014 @@
+/*
+  This file is part of TALER
+  (C) 2014-2020 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_post-orders-ID-abort.c
+ * @brief handling of POST /orders/$ID/abort requests
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_exchange_service.h>
+#include "taler-merchant-httpd_exchanges.h"
+
+
+/**
+ * How long to wait before giving up processing with the exchange?
+ */
+#define ABORT_TIMEOUT (GNUNET_TIME_relative_multiply 
(GNUNET_TIME_UNIT_SECONDS, \
+                                                      30))
+
+/**
+ * How often do we retry the (complex!) database transaction?
+ */
+#define MAX_RETRIES 5
+
+/**
+ * Information we keep for an individual call to the /abort handler.
+ */
+struct AbortContext;
+
+/**
+ * Information kept during a /abort request for each coin.
+ */
+struct RefundDetails
+{
+
+  /**
+   * Public key of the coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Reference to the main AbortContext
+   */
+  struct AbortContext *ac;
+
+  /**
+   * Handle to the refund operation we are performing for
+   * this coin, NULL after the operation is done.
+   */
+  struct TALER_EXCHANGE_RefundHandle *rh;
+
+  /**
+   * URL of the exchange that issued this coin.
+   */
+  char *exchange_url;
+
+  /**
+   * Body of the response from the exchange.  Note that the body returned MUST
+   * be freed (if non-NULL).
+   */
+  json_t *exchange_reply;
+
+  /**
+   * Amount this coin contributes to the total purchase price.
+   * This amount includes the deposit fee.
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Offset of this coin into the `rd` array of all coins in the
+   * @e ac.
+   */
+  unsigned int index;
+
+  /**
+   * HTTP status returned by the exchange (if any).
+   */
+  unsigned int http_status;
+
+  /**
+   * Did we try to process this refund yet?
+   */
+  bool processed;
+
+};
+
+
+/**
+ * Information we keep for an individual call to the /abort handler.
+ */
+struct AbortContext
+{
+
+  /**
+   * Hashed contract terms (according to client).
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * Context for our operation.
+   */
+  struct TMH_HandlerContext *hc;
+
+  /**
+   * Stored in a DLL.
+   */
+  struct AbortContext *next;
+
+  /**
+   * Stored in a DLL.
+   */
+  struct AbortContext *prev;
+
+  /**
+   * Array with @e coins_cnt coins we are despositing.
+   */
+  struct RefundDetails *rd;
+
+  /**
+   * MHD connection to return to
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Task called when the (suspended) processing for
+   * the /abort request times out.
+   * Happens when we don't get a response from the exchange.
+   */
+  struct GNUNET_SCHEDULER_Task *timeout_task;
+
+  /**
+   * Response to return, NULL if we don't have one yet.
+   */
+  struct MHD_Response *response;
+
+  /**
+   * Handle to the exchange that we are doing the abortment with.
+   * (initially NULL while @e fo is trying to find a exchange).
+   */
+  struct TALER_EXCHANGE_Handle *mh;
+
+  /**
+   * Handle for operation to lookup /keys (and auditors) from
+   * the exchange used for this transaction; NULL if no operation is
+   * pending.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * URL of the exchange used for the last @e fo.
+   */
+  const char *current_exchange;
+
+  /**
+   * Number of coins this abort is for.  Length of the @e rd array.
+   */
+  unsigned int coins_cnt;
+
+  /**
+   * How often have we retried the 'main' transaction?
+   */
+  unsigned int retry_counter;
+
+  /**
+   * Number of transactions still pending.  Initially set to
+   * @e coins_cnt, decremented on each transaction that
+   * successfully finished.
+   */
+  unsigned int pending;
+
+  /**
+   * Number of transactions still pending for the currently selected
+   * exchange.  Initially set to the number of coins started at the
+   * exchange, decremented on each transaction that successfully
+   * finished.  Once it hits zero, we pick the next exchange.
+   */
+  unsigned int pending_at_ce;
+
+  /**
+   * HTTP status code to use for the reply, i.e 200 for "OK".
+   * Special value UINT_MAX is used to indicate hard errors
+   * (no reply, return #MHD_NO).
+   */
+  unsigned int response_code;
+
+  /**
+   * #GNUNET_NO if the @e connection was not suspended,
+   * #GNUNET_YES if the @e connection was suspended,
+   * #GNUNET_SYSERR if @e connection was resumed to as
+   * part of #MH_force_ac_resume during shutdown.
+   */
+  int suspended;
+
+};
+
+
+/**
+ * Head of active abort context DLL.
+ */
+static struct AbortContext *ac_head;
+
+/**
+ * Tail of active abort context DLL.
+ */
+static struct AbortContext *ac_tail;
+
+
+/**
+ * Abort all pending /deposit operations.
+ *
+ * @param ac abort context to abort
+ */
+static void
+abort_refunds (struct AbortContext *ac)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Aborting pending /deposit operations\n");
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
+  {
+    struct RefundDetails *rdi = &ac->rd[i];
+
+    if (NULL != rdi->rh)
+    {
+      TALER_EXCHANGE_refund_cancel (rdi->rh);
+      rdi->rh = NULL;
+    }
+  }
+}
+
+
+/**
+ * Force all abort contexts to be resumed as we are about
+ * to shut down MHD.
+ */
+void
+TMH_force_ac_resume ()
+{
+  for (struct AbortContext *ac = ac_head;
+       NULL != ac;
+       ac = ac->next)
+  {
+    abort_refunds (ac);
+    if (NULL != ac->timeout_task)
+    {
+      GNUNET_SCHEDULER_cancel (ac->timeout_task);
+      ac->timeout_task = NULL;
+    }
+    if (GNUNET_YES == ac->suspended)
+    {
+      ac->suspended = GNUNET_SYSERR;
+      MHD_resume_connection (ac->connection);
+    }
+  }
+}
+
+
+/**
+ * Resume the given abort context and send the given response.
+ * Stores the response in the @a ac and signals MHD to resume
+ * the connection.  Also ensures MHD runs immediately.
+ *
+ * @param ac abortment context
+ * @param response_code response code to use
+ * @param response response data to send back
+ */
+static void
+resume_abort_with_response (struct AbortContext *ac,
+                            unsigned int response_code,
+                            struct MHD_Response *response)
+{
+  abort_refunds (ac);
+  ac->response_code = response_code;
+  ac->response = response;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Resuming /abort handling as exchange interaction is done 
(%u)\n",
+              response_code);
+  if (NULL != ac->timeout_task)
+  {
+    GNUNET_SCHEDULER_cancel (ac->timeout_task);
+    ac->timeout_task = NULL;
+  }
+  GNUNET_assert (GNUNET_YES == ac->suspended);
+  ac->suspended = GNUNET_NO;
+  MHD_resume_connection (ac->connection);
+  TMH_trigger_daemon (); /* we resumed, kick MHD */
+}
+
+
+/**
+ * Resume abortment processing with an error.
+ *
+ * @param ac operation to resume
+ * @param http_status http status code to return
+ * @param ec taler error code to return
+ * @param msg human readable error message
+ */
+static void
+resume_abort_with_error (struct AbortContext *ac,
+                         unsigned int http_status,
+                         enum TALER_ErrorCode ec,
+                         const char *msg)
+{
+  resume_abort_with_response (ac,
+                              http_status,
+                              TALER_MHD_make_error (ec,
+                                                    msg));
+}
+
+
+/**
+ * Generate a response that indicates abortment success.
+ *
+ * @param ac abortment context
+ */
+static void
+generate_success_response (struct AbortContext *ac)
+{
+  json_t *refunds;
+
+  refunds = json_array ();
+  if (NULL == refunds)
+  {
+    GNUNET_break (0);
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_JSON_ALLOCATION_FAILURE,
+                             "could not create JSON array");
+    return;
+  }
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
+  {
+    struct RefundDetails *rdi = &ac->rd[i];
+
+    if (0 !=
+        json_array_append_new (
+          refunds,
+          json_pack ("{s:I, s:O}",
+                     "exchange_http_status",
+                     (json_int_t) rdi->http_status,
+                     "exchange_reply",
+                     rdi->exchange_reply)))
+    {
+      json_decref (refunds);
+      GNUNET_break (0);
+      resume_abort_with_error (ac,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TALER_EC_JSON_ALLOCATION_FAILURE,
+                               "could not create JSON array");
+      return;
+    }
+  }
+
+  /* Resume and send back the response.  */
+  resume_abort_with_response (ac,
+                              MHD_HTTP_OK,
+                              TALER_MHD_make_json_pack ("{s:o}",
+                                                        "refunds",
+                                                        refunds));
+}
+
+
+/**
+ * Custom cleanup routine for a `struct AbortContext`.
+ *
+ * @param cls the `struct AbortContext` to clean up.
+ */
+static void
+abort_context_cleanup (void *cls)
+{
+  struct AbortContext *ac = cls;
+
+  if (NULL != ac->timeout_task)
+  {
+    GNUNET_SCHEDULER_cancel (ac->timeout_task);
+    ac->timeout_task = NULL;
+  }
+  abort_refunds (ac);
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
+  {
+    struct RefundDetails *rdi = &ac->rd[i];
+
+    if (NULL != rdi->exchange_reply)
+    {
+      json_decref (rdi->exchange_reply);
+      rdi->exchange_reply = NULL;
+    }
+    GNUNET_free (rdi->exchange_url);
+  }
+  GNUNET_free (ac->rd);
+  if (NULL != ac->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+    ac->fo = NULL;
+  }
+  if (NULL != ac->response)
+  {
+    MHD_destroy_response (ac->response);
+    ac->response = NULL;
+  }
+  GNUNET_CONTAINER_DLL_remove (ac_head,
+                               ac_tail,
+                               ac);
+  GNUNET_free (ac);
+}
+
+
+/**
+ * Find the exchange we need to talk to for the next
+ * pending deposit permission.
+ *
+ * @param ac abortment context we are processing
+ */
+static void
+find_next_exchange (struct AbortContext *ac);
+
+
+/**
+ * Function called with the result from the exchange (to be
+ * passed back to the wallet).
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ * @param refund_fee fee the exchange charged
+ * @param sign_key exchange key used to sign @a obj, or NULL
+ * @param signature the actual signature, or NULL on error
+ */
+static void
+refund_cb (void *cls,
+           const struct TALER_EXCHANGE_HttpResponse *hr,
+           const struct TALER_Amount *refund_fee,
+           const struct TALER_ExchangePublicKeyP *sign_key,
+           const struct TALER_ExchangeSignatureP *signature)
+{
+  struct RefundDetails *rd = cls;
+  struct AbortContext *ac = rd->ac;
+
+  (void) sign_key;
+  (void) signature;
+  (void) refund_fee;
+  rd->rh = NULL;
+  rd->http_status = hr->http_status;
+  rd->exchange_reply = json_incref ((json_t*) hr->reply);
+  ac->pending_at_ce--;
+  if (0 == ac->pending_at_ce)
+    find_next_exchange (ac);
+}
+
+
+/**
+ * Function called with the result of our exchange lookup.
+ *
+ * @param cls the `struct AbortContext`
+ * @param hr HTTP response details
+ * @param payto_uri payto://-URI of the exchange
+ * @param exchange_handle NULL if exchange was not found to be acceptable
+ * @param wire_fee current applicable fee for dealing with @a exchange_handle,
+ *        NULL if not available
+ * @param exchange_trusted true if this exchange is
+ *        trusted by config
+ */
+static void
+process_abort_with_exchange (void *cls,
+                             const struct TALER_EXCHANGE_HttpResponse *hr,
+                             struct TALER_EXCHANGE_Handle *exchange_handle,
+                             const char *payto_uri,
+                             const struct TALER_Amount *wire_fee,
+                             bool exchange_trusted)
+{
+  struct AbortContext *ac = cls;
+
+  (void) payto_uri;
+  (void) exchange_trusted;
+  ac->fo = NULL;
+  GNUNET_assert (GNUNET_YES == ac->suspended);
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    /* The request failed somehow */
+    GNUNET_break_op (0);
+    resume_abort_with_response (
+      ac,
+      MHD_HTTP_FAILED_DEPENDENCY,
+      TALER_MHD_make_json_pack (
+        (NULL != hr->reply)
+        ? "{s:s, s:I, s:I, s:I, s:O}"
+        : "{s:s, s:I, s:I, s:I}",
+        "hint",
+        "failed to obtain meta-data from exchange",
+        "code",
+        (json_int_t) TALER_EC_ABORT_EXCHANGE_KEYS_FAILURE,
+        "exchange_http_status",
+        (json_int_t) hr->http_status,
+        "exchange_code",
+        (json_int_t) hr->ec,
+        "exchange_reply",
+        hr->reply));
+    return;
+  }
+  /* Initiate refund operation for all coins of
+     the current exchange (!) */
+  GNUNET_assert (0 == ac->pending_at_ce);
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
+  {
+    struct RefundDetails *rdi = &ac->rd[i];
+
+    if (rdi->processed)
+      continue;
+    GNUNET_assert (NULL == rdi->rh);
+    if (0 != strcmp (rdi->exchange_url,
+                     ac->current_exchange))
+      continue;
+    rdi->processed = true;
+    ac->pending--;
+    rdi->rh = TALER_EXCHANGE_refund (exchange_handle,
+                                     &rdi->amount_with_fee,
+                                     &ac->h_contract_terms,
+                                     &rdi->coin_pub,
+                                     0, /* rtransaction_id */
+                                     &ac->hc->instance->merchant_priv,
+                                     &refund_cb,
+                                     rdi);
+    if (NULL == rdi->rh)
+    {
+      GNUNET_break_op (0);
+      resume_abort_with_error (ac,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TALER_EC_ABORT_EXCHANGE_REFUND_FAILED,
+                               "Failed to start refund with exchange");
+      return;
+    }
+    ac->pending_at_ce++;
+  }
+}
+
+
+/**
+ * Begin of the DB transaction.  If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param ac abortment context to transact
+ */
+static void
+begin_transaction (struct AbortContext *ac);
+
+
+/**
+ * Find the exchange we need to talk to for the next
+ * pending deposit permission.
+ *
+ * @param ac abortment context we are processing
+ */
+static void
+find_next_exchange (struct AbortContext *ac)
+{
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
+  {
+    struct RefundDetails *rdi = &ac->rd[i];
+
+    if (! rdi->processed)
+    {
+      ac->current_exchange = rdi->exchange_url;
+      ac->fo = TMH_EXCHANGES_find_exchange (ac->current_exchange,
+                                            NULL,
+                                            GNUNET_NO,
+                                            &process_abort_with_exchange,
+                                            ac);
+      if (NULL == ac->fo)
+      {
+        GNUNET_break (0);
+        resume_abort_with_error (ac,
+                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                 TALER_EC_ABORT_EXCHANGE_LOOKUP_FAILED,
+                                 "Failed to lookup exchange by URL");
+        return;
+      }
+      return;
+    }
+  }
+  ac->current_exchange = NULL;
+  GNUNET_assert (0 == ac->pending);
+  /* We are done with all the HTTP requests, go back and try
+     the 'big' database transaction! (It should work now!) */
+  begin_transaction (ac);
+}
+
+
+/**
+ * Function called with information about a coin that was deposited.
+ *
+ * @param cls closure
+ * @param exchange_url exchange where @a coin_pub was deposited
+ * @param coin_pub public key of the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee wire fee the exchange of this coin charges
+ */
+static void
+refund_coins (void *cls,
+              const char *exchange_url,
+              const struct TALER_CoinSpendPublicKeyP *coin_pub,
+              const struct TALER_Amount *amount_with_fee,
+              const struct TALER_Amount *deposit_fee,
+              const struct TALER_Amount *refund_fee,
+              const struct TALER_Amount *wire_fee)
+{
+  struct AbortContext *ac = cls;
+  struct GNUNET_TIME_Absolute now;
+
+  (void) amount_with_fee;
+  (void) deposit_fee;
+  (void) refund_fee;
+  (void) wire_fee;
+  now = GNUNET_TIME_absolute_get ();
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
+  {
+    struct RefundDetails *rdi = &ac->rd[i];
+    enum GNUNET_DB_QueryStatus qs;
+
+    if ( (0 !=
+          GNUNET_memcmp (coin_pub,
+                         &rdi->coin_pub)) ||
+         (0 !=
+          strcmp (exchange_url,
+                  rdi->exchange_url)) )
+      continue; /* not in request */
+
+    /* Store refund in DB */
+    qs = TMH_db->refund_coin (TMH_db->cls,
+                              ac->hc->instance->settings.id,
+                              &ac->h_contract_terms,
+                              now,
+                              coin_pub,
+                              /* justification */
+                              "incomplete abortment aborted");
+    if (0 > qs)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      {
+        begin_transaction (ac);
+        return;
+      }
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      resume_abort_with_error (ac,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+                               "Merchant database error storing abort-refund");
+      return;
+    }
+  } /* for all coins */
+}
+
+
+/**
+ * Begin of the DB transaction.  If required (from soft/serialization errors),
+ * the transaction can be restarted here.
+ *
+ * @param ac abortment context to transact
+ */
+static void
+begin_transaction (struct AbortContext *ac)
+{
+  enum GNUNET_DB_QueryStatus qs;
+
+  /* Avoid re-trying transactions on soft errors forever! */
+  if (ac->retry_counter++ > MAX_RETRIES)
+  {
+    GNUNET_break (0);
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_STORE_TRANSACTION_ERROR,
+                             "Soft merchant database error: retry counter 
exceeded");
+    return;
+  }
+  GNUNET_assert (GNUNET_YES == ac->suspended);
+
+  /* First, try to see if we have all we need already done */
+  TMH_db->preflight (TMH_db->cls);
+  if (GNUNET_OK !=
+      TMH_db->start (TMH_db->cls,
+                     "run abort"))
+  {
+    GNUNET_break (0);
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR,
+                             "Merchant database error (could not begin 
transaction)");
+    return;
+  }
+
+  /* check payment was indeed incomplete
+     (now that we are in the transaction scope!) */
+  {
+    struct GNUNET_HashCode h_contract_terms;
+    bool paid;
+
+    qs = TMH_db->lookup_order_status (TMH_db->cls,
+                                      ac->hc->instance->settings.id,
+                                      ac->hc->infix,
+                                      &h_contract_terms,
+                                      &paid);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      /* Always report on hard error to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      TMH_db->rollback (TMH_db->cls);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      {
+        begin_transaction (ac);
+        return;
+      }
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      resume_abort_with_error (ac,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR,
+                               "Merchant database error");
+      return;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      TMH_db->rollback (TMH_db->cls);
+      resume_abort_with_error (ac,
+                               MHD_HTTP_NOT_FOUND,
+                               TALER_EC_ABORT_CONTRACT_NOT_FOUND,
+                               "Could not find contract");
+      return;
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      if (paid)
+      {
+        /* Payment is complete, refuse to abort. */
+        TMH_db->rollback (TMH_db->cls);
+        resume_abort_with_error (ac,
+                                 MHD_HTTP_PRECONDITION_FAILED,
+                                 
TALER_EC_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
+                                 "Payment was complete, refusing to abort");
+        return;
+      }
+    }
+    if (0 !=
+        GNUNET_memcmp (&ac->h_contract_terms,
+                       &h_contract_terms))
+    {
+      GNUNET_break_op (0);
+      resume_abort_with_error (ac,
+                               MHD_HTTP_FORBIDDEN,
+                               TALER_EC_ABORT_CONTRACT_HASH_MISSMATCH,
+                               "Provided hash does not match order on file");
+      return;
+    }
+  }
+
+  /* Mark all deposits we have in our database for the order as refunded. */
+  qs = TMH_db->lookup_deposits (TMH_db->cls,
+                                ac->hc->instance->settings.id,
+                                &ac->h_contract_terms,
+                                &refund_coins,
+                                ac);
+  if (0 > qs)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      begin_transaction (ac);
+      return;
+    }
+    /* Always report on hard error as well to enable diagnostics */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR,
+                             "Merchant database error");
+    return;
+  }
+
+  qs = TMH_db->commit (TMH_db->cls);
+  if (0 > qs)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      begin_transaction (ac);
+      return;
+    }
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+                             "Merchant database error: could not commit");
+    return;
+  }
+
+  /* At this point, the refund got correctly committed
+     into the database. Tell exchange about abort/refund. */
+  if (ac->pending > 0)
+  {
+    find_next_exchange (ac);
+    return;
+  }
+  generate_success_response (ac);
+}
+
+
+/**
+ * Try to parse the abort request into the given abort context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param connection HTTP connection we are receiving abortment on
+ * @param root JSON upload with abortment data
+ * @param ac context we use to handle the abortment
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO on failure (response was queued with MHD)
+ *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ */
+static enum GNUNET_GenericReturnValue
+parse_abort (struct MHD_Connection *connection,
+             struct TMH_HandlerContext *hc,
+             struct AbortContext *ac)
+{
+  json_t *coins;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("coins",
+                           &coins),
+    GNUNET_JSON_spec_fixed_auto ("h_contract",
+                                 &ac->h_contract_terms),
+
+    GNUNET_JSON_spec_end ()
+  };
+  enum GNUNET_GenericReturnValue res;
+
+  res = TALER_MHD_parse_json_data (connection,
+                                   hc->request_body,
+                                   spec);
+  if (GNUNET_YES != res)
+  {
+    GNUNET_break_op (0);
+    return res;
+  }
+  ac->coins_cnt = json_array_size (coins);
+  if (0 == ac->coins_cnt)
+  {
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_ABORT_COINS_ARRAY_EMPTY,
+                                       "coins");
+  }
+  /* note: 1 coin = 1 deposit confirmation expected */
+  ac->pending = ac->coins_cnt;
+  ac->rd = GNUNET_new_array (ac->coins_cnt,
+                             struct RefundDetails);
+  /* This loop populates the array 'rd' in 'ac' */
+  {
+    unsigned int coins_index;
+    json_t *coin;
+    json_array_foreach (coins, coins_index, coin)
+    {
+      struct RefundDetails *rd = &ac->rd[coins_index];
+      const char *exchange_url;
+      struct GNUNET_JSON_Specification ispec[] = {
+        TALER_JSON_spec_amount ("contribution",
+                                &rd->amount_with_fee),
+        GNUNET_JSON_spec_string ("exchange_url",
+                                 &exchange_url),
+        GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                     &rd->coin_pub),
+        GNUNET_JSON_spec_end ()
+      };
+
+      res = TALER_MHD_parse_json_data (connection,
+                                       coin,
+                                       ispec);
+      if (GNUNET_YES != res)
+      {
+        GNUNET_JSON_parse_free (spec);
+        GNUNET_break_op (0);
+        return res;
+      }
+      rd->exchange_url = GNUNET_strdup (exchange_url);
+      rd->index = coins_index;
+      rd->ac = ac;
+    }
+  }
+  GNUNET_JSON_parse_free (spec);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Handling /abort for order `%s' with contract hash `%s'\n",
+              ac->hc->infix,
+              GNUNET_h2s (&ac->h_contract_terms));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Handle a timeout for the processing of the abort request.
+ *
+ * @param cls our `struct AbortContext`
+ */
+static void
+handle_abort_timeout (void *cls)
+{
+  struct AbortContext *ac = cls;
+
+  ac->timeout_task = NULL;
+  GNUNET_assert (GNUNET_YES == ac->suspended);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Resuming abort with error after timeout\n");
+  if (NULL != ac->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+    ac->fo = NULL;
+  }
+  resume_abort_with_error (ac,
+                           MHD_HTTP_REQUEST_TIMEOUT,
+                           TALER_EC_ABORT_EXCHANGE_TIMEOUT,
+                           "likely the exchange did not reply quickly enough");
+}
+
+
+/**
+ * Abort payment for a claimed order.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          struct TMH_HandlerContext *hc)
+{
+  struct AbortContext *ac = hc->ctx;
+
+  if (NULL == ac)
+  {
+    ac = GNUNET_new (struct AbortContext);
+    GNUNET_CONTAINER_DLL_insert (ac_head,
+                                 ac_tail,
+                                 ac);
+    ac->connection = connection;
+    ac->hc = hc;
+    hc->ctx = ac;
+    hc->cc = &abort_context_cleanup;
+  }
+  if (GNUNET_SYSERR == ac->suspended)
+    return MHD_NO; /* during shutdown, we don't generate any more replies */
+  if (0 != ac->response_code)
+  {
+    MHD_RESULT res;
+
+    /* We are *done* processing the request,
+       just queue the response (!) */
+    if (UINT_MAX == ac->response_code)
+    {
+      GNUNET_break (0);
+      return MHD_NO; /* hard error */
+    }
+    res = MHD_queue_response (connection,
+                              ac->response_code,
+                              ac->response);
+    MHD_destroy_response (ac->response);
+    ac->response = NULL;
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Queueing response (%u) for /abort (%s).\n",
+                (unsigned int) ac->response_code,
+                res ? "OK" : "FAILED");
+    return res;
+  }
+  {
+    enum GNUNET_GenericReturnValue ret;
+
+    ret = parse_abort (connection,
+                       hc,
+                       ac);
+    if (GNUNET_OK != ret)
+      return (GNUNET_NO == ret)
+             ? MHD_YES
+             : MHD_NO;
+  }
+
+  /* Abort not finished, suspend while we interact with the exchange */
+  GNUNET_assert (GNUNET_NO == ac->suspended);
+  MHD_suspend_connection (connection);
+  ac->suspended = GNUNET_YES;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Suspending abort handling while working with the exchange\n");
+  ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_TIMEOUT,
+                                                   &handle_abort_timeout,
+                                                   ac);
+  begin_transaction (ac);
+  return MHD_YES;
+}
+
+
+/* end of taler-merchant-httpd_post-orders-ID-abort.c */
diff --git a/src/backend/taler-merchant-httpd_tip-authorize.h 
b/src/backend/taler-merchant-httpd_post-orders-ID-abort.h
similarity index 53%
rename from src/backend/taler-merchant-httpd_tip-authorize.h
rename to src/backend/taler-merchant-httpd_post-orders-ID-abort.h
index 1f7f44e..77f55bc 100644
--- a/src/backend/taler-merchant-httpd_tip-authorize.h
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2017 Taler Systems SA
+  (C) 2014-2020 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,33 +14,36 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_tip-authorize.h
- * @brief headers for /tip-authorize handler
+ * @file backend/taler-merchant-httpd_post-orders-ID-abort.h
+ * @brief headers for POST /orders/$ID/abort handler
+ * @author Marcello Stanisci
  * @author Christian Grothoff
  */
-#ifndef TALER_MERCHANT_HTTPD_TIP_AUTHORIZE_H
-#define TALER_MERCHANT_HTTPD_TIP_AUTHORIZE_H
+#ifndef TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_ABORT_H
+#define TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_ABORT_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
+
+/**
+ * Force all abort contexts to be resumed as we are about
+ * to shut down MHD.
+ */
+void
+TMH_force_ac_resume (void);
+
+
 /**
- * Manages a /tip-authorize call, creating a TIP ID and storing the
- * authorization in our DB.
+ * Abort payment for a claimed order.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_tip_authorize (struct TMH_RequestHandler *rh,
+TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
                           struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi);
+                          struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-claim.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-claim.c
new file mode 100644
index 0000000..c6dc7e6
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-claim.c
@@ -0,0 +1,252 @@
+/*
+  This file is part of TALER
+  (C) 2014, 2015, 2016, 2018, 2020 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_post-orders-ID-claim.c
+ * @brief headers for POST /orders/$ID/claim handler
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_post-orders-ID-claim.h"
+
+
+/**
+ * How often do we retry the database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Run transaction to claim @a order_id for @a nonce.
+ *
+ * @param instance_id instance to claim order at
+ * @param order_id order to claim
+ * @param nonce nonce to use for the claim
+ * @param[out] contract_terms set to the resulting contract terms
+ *             (for any non-negative result;
+ * @return transaction status code
+ *         #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the order was claimed by a 
different
+ *         nonce (@a contract_terms set to non-NULL)
+ *                OR if the order is is unknown (@a contract_terms is NULL)
+ *         #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the order was successfully 
claimed
+ */
+static enum GNUNET_DB_QueryStatus
+claim_order (const char *instance_id,
+             const char *order_id,
+             const char *nonce,
+             json_t **contract_terms)
+{
+  enum GNUNET_DB_QueryStatus qs;
+
+  if (GNUNET_OK !=
+      TMH_db->start (TMH_db->cls,
+                     "claim order"))
+  {
+    GNUNET_break (0);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  {
+    uint64_t order_serial;
+
+    qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                        order_id,
+                                        instance_id,
+                                        contract_terms,
+                                        &order_serial);
+  }
+  if (0 > qs)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    return qs;
+  }
+
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    /* see if we have this order in our table of unclaimed orders */
+    qs = TMH_db->lookup_order (TMH_db->cls,
+                               instance_id,
+                               order_id,
+                               contract_terms);
+    if (0 >= qs)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      return qs;
+    }
+    GNUNET_assert (NULL != contract_terms);
+    GNUNET_assert (0 ==
+                   json_object_set_new (*contract_terms,
+                                        "nonce",
+                                        json_string (nonce)));
+    qs = TMH_db->insert_contract_terms (TMH_db->cls,
+                                        instance_id,
+                                        order_id,
+                                        *contract_terms);
+    if (0 > qs)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      json_decref (*contract_terms);
+      *contract_terms = NULL;
+      return qs;
+    }
+    qs = TMH_db->commit (TMH_db->cls);
+    if (0 > qs)
+      return qs;
+    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+  }
+  else
+  {
+    const char *stored_nonce;
+
+    TMH_db->rollback (TMH_db->cls);
+    GNUNET_assert (NULL != *contract_terms);
+    stored_nonce
+      = json_string_value (json_object_get (*contract_terms,
+                                            "nonce"));
+    if (NULL == stored_nonce)
+    {
+      /* this should not be possible: contract_terms should always
+         have a nonce! */
+      GNUNET_break (0);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+    if (0 != strcmp (stored_nonce,
+                     nonce))
+    {
+      return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+    }
+    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+  }
+}
+
+
+/**
+ * Manage a POST /orders/$ID/claim request.  Allows the client to
+ * claim the order (unless already claims) and creates the respective
+ * contract.  Returns the contract terms.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          struct TMH_HandlerContext *hc)
+{
+  const char *order_id = hc->infix;
+  const char *nonce;
+  enum GNUNET_DB_QueryStatus qs;
+  json_t *contract_terms;
+
+  {
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("nonce",
+                               &nonce),
+      GNUNET_JSON_spec_end ()
+    };
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+  contract_terms = NULL;
+  for (unsigned int i = 0; i<MAX_RETRIES; i++)
+  {
+    TMH_db->preflight (TMH_db->cls);
+    qs = claim_order (hc->instance->settings.id,
+                      order_id,
+                      nonce,
+                      &contract_terms);
+    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+      break;
+  }
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_ORDERS_CLAIM_HARD_DB_ERROR,
+                                       "Failed to run DB transaction to claim 
order");
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_ORDERS_CLAIM_SOFT_DB_ERROR,
+                                       "Failed to serialize DB transaction to 
claim order");
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    if (NULL == contract_terms)
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_ORDERS_CLAIM_NOT_FOUND,
+                                         "unknown order id");
+    /* already claimed! */
+    json_decref (contract_terms);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       TALER_EC_ORDERS_ALREADY_CLAIMED,
+                                       "order already claimed");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    GNUNET_assert (NULL != contract_terms);
+    break; /* Good! return signature (below) */
+  }
+
+  /* create proposal signature */
+  {
+    struct GNUNET_CRYPTO_EddsaSignature merchant_sig;
+    struct TALER_ProposalDataPS pdps = {
+      .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT),
+      .purpose.size = htonl (sizeof (pdps))
+    };
+
+    if (GNUNET_OK !=
+        TALER_JSON_hash (contract_terms,
+                         &pdps.hash))
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_INTERNAL_LOGIC_ERROR,
+                                         "Could not hash order");
+    }
+
+    GNUNET_CRYPTO_eddsa_sign (&hc->instance->merchant_priv.eddsa_priv,
+                              &pdps,
+                              &merchant_sig);
+    return TALER_MHD_reply_json_pack (connection,
+                                      MHD_HTTP_OK,
+                                      "{ s:o, s:o }",
+                                      "contract_terms",
+                                      contract_terms,
+                                      "sig",
+                                      GNUNET_JSON_from_data_auto (
+                                        &merchant_sig));
+  }
+}
+
+
+/* end of taler-merchant-httpd_post-orders-ID-claim.c */
diff --git a/src/backend/taler-merchant-httpd_check-payment.h 
b/src/backend/taler-merchant-httpd_post-orders-ID-claim.h
similarity index 52%
rename from src/backend/taler-merchant-httpd_check-payment.h
rename to src/backend/taler-merchant-httpd_post-orders-ID-claim.h
index e94645d..5da1607 100644
--- a/src/backend/taler-merchant-httpd_check-payment.h
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-claim.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2017 Taler Systems SA
+  (C) 2014, 2015, 2020 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,35 +14,29 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_tip-query.h
- * @brief headers for /tip-query handler
+ * @file backend/taler-merchant-httpd_post-orders-ID-claim.h
+ * @brief headers for POST /orders/$ID/claim handler
+ * @author Marcello Stanisci
  * @author Christian Grothoff
- * @author Florian Dold
  */
-#ifndef TALER_MERCHANT_HTTPD_CHECK_PAYMENT_H
-#define TALER_MERCHANT_HTTPD_CHECK_PAYMENT_H
+#ifndef TALER_MERCHANT_HTTPD_POST_ORDERS_ID_CLAIM_H
+#define TALER_MERCHANT_HTTPD_POST_ORDERS_ID_CLAIM_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 /**
- * Manages a /check-payment call, checking the status
- * of a payment and, if necessary, constructing the URL
- * for a payment redirect URL.
+ * Manage a POST /orders/$ID/claim request.  Allows the client to
+ * claim the order (unless already claims) and creates the respective
+ * contract.  Returns the contract terms.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_check_payment (struct TMH_RequestHandler *rh,
+TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh,
                           struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi);
+                          struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_pay.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
similarity index 56%
rename from src/backend/taler-merchant-httpd_pay.c
rename to src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 7a1b7fd..cf607fc 100644
--- a/src/backend/taler-merchant-httpd_pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -18,22 +18,20 @@
 */
 
 /**
- * @file backend/taler-merchant-httpd_pay.c
- * @brief handling of /pay requests
+ * @file backend/taler-merchant-httpd_post-orders-ID-pay.c
+ * @brief handling of POST /orders/$ID/pay requests
  * @author Marcello Stanisci
  * @author Christian Grothoff
  * @author Florian Dold
  */
 #include "platform.h"
-#include <jansson.h>
-#include <gnunet/gnunet_util_lib.h>
 #include <taler/taler_signatures.h>
 #include <taler/taler_json_lib.h>
 #include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd.h"
 #include "taler-merchant-httpd_auditors.h"
 #include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_refund.h"
+#include "taler-merchant-httpd_post-orders-ID-pay.h"
+#include "taler-merchant-httpd_private-get-orders.h"
 
 
 /**
@@ -48,12 +46,12 @@
 #define MAX_RETRIES 5
 
 /**
- * Information we keep for an individual call to the /pay handler.
+ * Information we keep for an individual call to the pay handler.
  */
 struct PayContext;
 
 /**
- * Information kept during a /pay request for each coin.
+ * Information kept during a pay request for each coin.
  */
 struct DepositConfirmation
 {
@@ -75,9 +73,9 @@ struct DepositConfirmation
   char *exchange_url;
 
   /**
-   * Denomination of this coin.
+   * Hash of the denomination of this coin.
    */
-  struct TALER_DenominationPublicKey denom;
+  struct GNUNET_HashCode h_denom;
 
   /**
    * Amount this coin contributes to the total purchase price.
@@ -122,14 +120,9 @@ struct DepositConfirmation
   unsigned int index;
 
   /**
-   * #GNUNET_YES if we found this coin in the database.
-   */
-  int found_in_db;
-
-  /**
-   * #GNUNET_YES if this coin was refunded.
+   * true if we found this coin in the database.
    */
-  int refunded;
+  bool found_in_db;
 
 };
 
@@ -140,12 +133,6 @@ struct DepositConfirmation
 struct PayContext
 {
 
-  /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
-   */
-  struct TM_HandlerContext hc;
-
   /**
    * Stored in a DLL.
    */
@@ -167,21 +154,15 @@ struct PayContext
   struct MHD_Connection *connection;
 
   /**
-   * Instance of the payment's instance (in JSON format)
+   * Details about the client's request.
    */
-  struct MerchantInstance *mi;
+  struct TMH_HandlerContext *hc;
 
   /**
    * What wire method (of the @e mi) was selected by the wallet?
    * Set in #parse_pay().
    */
-  struct WireMethod *wm;
-
-  /**
-   * Proposal data for the proposal that is being
-   * paid for in this context.
-   */
-  json_t *contract_terms;
+  struct TMH_WireMethod *wm;
 
   /**
    * Task called when the (suspended) processing for
@@ -195,12 +176,6 @@ struct PayContext
    */
   struct MHD_Response *response;
 
-  /**
-   * Handle to the exchange that we are doing the payment with.
-   * (initially NULL while @e fo is trying to find a exchange).
-   */
-  struct TALER_EXCHANGE_Handle *mh;
-
   /**
    * Handle for operation to lookup /keys (and auditors) from
    * the exchange used for this transaction; NULL if no operation is
@@ -230,9 +205,9 @@ struct PayContext
   char *order_id;
 
   /**
-   * Fulfillment URL from @e contract_terms.
+   * Serial number of this order in the database (set once we did the lookup).
    */
-  char *fulfillment_url;
+  uint64_t order_serial;
 
   /**
    * Hashed proposal.
@@ -371,14 +346,9 @@ struct PayContext
   int suspended;
 
   /**
-   * #GNUNET_YES if we already tried a forced /keys download.
-   */
-  int tried_force_keys;
-
-  /**
-   * Which operational mode is the /pay request made in?
+   * true if we already tried a forced /keys download.
    */
-  enum { PC_MODE_PAY, PC_MODE_ABORT_REFUND } mode;
+  bool tried_force_keys;
 
 };
 
@@ -422,7 +392,7 @@ abort_deposit (struct PayContext *pc)
  * to shut down MHD.
  */
 void
-MH_force_pc_resume ()
+TMH_force_pc_resume ()
 {
   for (struct PayContext *pc = pc_head;
        NULL != pc;
@@ -457,6 +427,7 @@ resume_pay_with_response (struct PayContext *pc,
                           unsigned int response_code,
                           struct MHD_Response *response)
 {
+  abort_deposit (pc);
   pc->response_code = response_code;
   pc->response = response;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -495,117 +466,34 @@ resume_pay_with_error (struct PayContext *pc,
 }
 
 
-/**
- * Generate a response that indicates payment success.
- *
- * @param pc payment context
- */
-static void
-generate_success_response (struct PayContext *pc)
-{
-  json_t *refunds;
-  struct GNUNET_CRYPTO_EddsaSignature sig;
-
-  /* Check for applicable refunds */
-  {
-    enum TALER_ErrorCode ec;
-    const char *errmsg;
-
-    refunds = TM_get_refund_json (pc->mi,
-                                  &pc->h_contract_terms,
-                                  &ec,
-                                  &errmsg);
-    /* We would get an EMPTY array back on success if there
-       are no refunds, but not NULL. So NULL is always an error. */
-    if (NULL == refunds)
-    {
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             ec,
-                             errmsg);
-      return;
-    }
-  }
-
-  /* Sign on our end (as the payment did go through, even if it may
-     have been refunded already) */
-  {
-    struct PaymentResponsePS mr = {
-      .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
-      .purpose.size = htonl (sizeof (mr)),
-      .h_contract_terms = pc->h_contract_terms
-    };
-
-    GNUNET_CRYPTO_eddsa_sign (&pc->mi->privkey.eddsa_priv,
-                              &mr,
-                              &sig);
-  }
-
-  /* Build the response */
-  {
-    json_t *resp;
-
-    resp = json_pack ("{s:O, s:o, s:o, s:o}",
-                      "contract_terms",
-                      pc->contract_terms,
-                      "sig",
-                      GNUNET_JSON_from_data_auto (&sig),
-                      "h_contract_terms",
-                      GNUNET_JSON_from_data (&pc->h_contract_terms,
-                                             sizeof (struct GNUNET_HashCode)),
-                      "refund_permissions",
-                      refunds);
-    if (NULL == resp)
-    {
-      GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_JSON_ALLOCATION_FAILURE,
-                             "could not build final response");
-      return;
-    }
-    resume_pay_with_response (pc,
-                              MHD_HTTP_OK,
-                              TALER_MHD_make_json (resp));
-    json_decref (resp);
-  }
-}
-
-
 /**
  * Custom cleanup routine for a `struct PayContext`.
  *
- * @param hc the `struct PayContext` to clean up.
+ * @param cls the `struct PayContext` to clean up.
  */
 static void
-pay_context_cleanup (struct TM_HandlerContext *hc)
+pay_context_cleanup (void *cls)
 {
-  struct PayContext *pc = (struct PayContext *) hc;
+  struct PayContext *pc = cls;
 
   if (NULL != pc->timeout_task)
   {
     GNUNET_SCHEDULER_cancel (pc->timeout_task);
     pc->timeout_task = NULL;
   }
-  TALER_MHD_parse_post_cleanup_callback (pc->json_parse_context);
   abort_deposit (pc);
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
 
-    if (NULL != dc->denom.rsa_public_key)
-    {
-      GNUNET_CRYPTO_rsa_public_key_free (dc->denom.rsa_public_key);
-      dc->denom.rsa_public_key = NULL;
-    }
     if (NULL != dc->ub_sig.rsa_signature)
     {
       GNUNET_CRYPTO_rsa_signature_free (dc->ub_sig.rsa_signature);
       dc->ub_sig.rsa_signature = NULL;
     }
-    GNUNET_free_non_null (dc->exchange_url);
+    GNUNET_free (dc->exchange_url);
   }
-  GNUNET_free_non_null (pc->dc);
+  GNUNET_free (pc->dc);
   if (NULL != pc->fo)
   {
     TMH_EXCHANGES_find_exchange_cancel (pc->fo);
@@ -616,14 +504,8 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
     MHD_destroy_response (pc->response);
     pc->response = NULL;
   }
-  if (NULL != pc->contract_terms)
-  {
-    json_decref (pc->contract_terms);
-    pc->contract_terms = NULL;
-  }
-  GNUNET_free_non_null (pc->order_id);
-  GNUNET_free_non_null (pc->session_id);
-  GNUNET_free_non_null (pc->fulfillment_url);
+  GNUNET_free (pc->order_id);
+  GNUNET_free (pc->session_id);
   GNUNET_CONTAINER_DLL_remove (pc_head,
                                pc_tail,
                                pc);
@@ -632,421 +514,155 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
 
 
 /**
- * Check whether the amount paid is sufficient to cover
- * the contract.
+ * Find the exchange we need to talk to for the next
+ * pending deposit permission.
  *
- * @param pc payment context to check
- * @return #GNUNET_OK if the payment is sufficient, #GNUNET_SYSERR if it is
- *         insufficient
+ * @param pc payment context we are processing
  */
-static int
-check_payment_sufficient (struct PayContext *pc)
-{
-  struct TALER_Amount acc_fee;
-  struct TALER_Amount acc_amount;
-  struct TALER_Amount final_amount;
-  struct TALER_Amount wire_fee_delta;
-  struct TALER_Amount wire_fee_customer_contribution;
-  struct TALER_Amount total_wire_fee;
-  struct TALER_Amount total_needed;
+static void
+find_next_exchange (struct PayContext *pc);
 
-  if (0 == pc->coins_cnt)
-  {
-    GNUNET_break_op (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_BAD_REQUEST,
-                           TALER_EC_PAY_PAYMENT_INSUFFICIENT,
-                           "insufficient funds (no coins!)");
-    return GNUNET_SYSERR;
-  }
 
-  acc_fee = pc->dc[0].deposit_fee;
-  total_wire_fee = pc->dc[0].wire_fee;
-  acc_amount = pc->dc[0].amount_with_fee;
+/**
+ * Begin of the DB transaction.  If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param pc payment context to transact
+ */
+static void
+begin_transaction (struct PayContext *pc);
 
-  /**
-   * This loops calculates what are the deposit fee / total
-   * amount with fee / and wire fee, for all the coins.
-   */
-  for (unsigned int i = 1; i<pc->coins_cnt; i++)
-  {
-    struct DepositConfirmation *dc = &pc->dc[i];
 
-    GNUNET_assert (GNUNET_YES == dc->found_in_db);
-    if ( (0 >
-          TALER_amount_add (&acc_fee,
-                            &dc->deposit_fee,
-                            &acc_fee)) ||
-         (0 >
-          TALER_amount_add (&acc_amount,
-                            &dc->amount_with_fee,
-                            &acc_amount)) )
+/**
+ * Callback to handle a deposit permission's response.
+ *
+ * @param cls a `struct DepositConfirmation` (i.e. a pointer
+ *   into the global array of confirmations and an index for this call
+ *   in that array). That way, the last executed callback can detect
+ *   that no other confirmations are on the way, and can pack a response
+ *   for the wallet
+ * @param hr HTTP response code details
+ * @param deposit_timestamp time when the exchange generated the deposit 
confirmation
+ * @param exchange_sig signature from the exchange over the deposit 
confirmation
+ * @param exchange_pub_key which key did the exchange use to create the @a 
exchange_sig
+ */
+static void
+deposit_cb (void *cls,
+            const struct TALER_EXCHANGE_HttpResponse *hr,
+            struct GNUNET_TIME_Absolute deposit_timestamp,
+            const struct TALER_ExchangeSignatureP *exchange_sig,
+            const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+  struct DepositConfirmation *dc = cls;
+  struct PayContext *pc = dc->pc;
+  enum GNUNET_DB_QueryStatus qs;
+
+  dc->dh = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
+  pc->pending_at_ce--;
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Deposit operation failed with HTTP code %u/%d\n",
+                hr->http_status,
+                (int) hr->ec);
+    /* Transaction failed */
+    if (5 == hr->http_status / 100)
     {
-      GNUNET_break (0);
-      /* Overflow in these amounts? Very strange. */
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_AMOUNT_OVERFLOW,
-                             "Overflow adding up amounts");
+      /* internal server error at exchange */
+      resume_pay_with_response (pc,
+                                MHD_HTTP_FAILED_DEPENDENCY,
+                                TALER_MHD_make_json_pack (
+                                  "{s:s, s:I, s:I, s:I}",
+                                  "hint",
+                                  "exchange had an internal server error",
+                                  "code",
+                                  (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+                                  "exchange_code",
+                                  (json_int_t) hr->ec,
+                                  "exchange_http_status",
+                                  (json_int_t) hr->http_status));
     }
-    if (1 ==
-        TALER_amount_cmp (&dc->deposit_fee,
-                          &dc->amount_with_fee))
+    else if (NULL == hr->reply)
     {
-      GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_BAD_REQUEST,
-                             TALER_EC_PAY_FEES_EXCEED_PAYMENT,
-                             "Deposit fees exceed coin's contribution");
-      return GNUNET_SYSERR;
+      /* We can't do anything meaningful here, the exchange did something 
wrong */
+      resume_pay_with_response (
+        pc,
+        MHD_HTTP_FAILED_DEPENDENCY,
+        TALER_MHD_make_json_pack (
+          "{s:s, s:I, s:I, s:I}",
+          "hint",
+          "exchange failed, response body not even in JSON",
+          "code",
+          (json_int_t) TALER_EC_PAY_EXCHANGE_REPLY_MALFORMED,
+          "exchange_code",
+          (json_int_t) hr->ec,
+          "exchange_http_status",
+          (json_int_t) hr->http_status));
     }
-
-    /* If exchange differs, add wire fee */
+    else
     {
-      int new_exchange = GNUNET_YES;
-
-      for (unsigned int j = 0; j<i; j++)
-        if (0 == strcasecmp (dc->exchange_url,
-                             pc->dc[j].exchange_url))
-        {
-          new_exchange = GNUNET_NO;
-          break;
-        }
-      if (GNUNET_YES == new_exchange)
-      {
-        if (GNUNET_OK !=
-            TALER_amount_cmp_currency (&total_wire_fee,
-                                       &dc->wire_fee))
-        {
-          GNUNET_break_op (0);
-          resume_pay_with_error (pc,
-                                 MHD_HTTP_PRECONDITION_FAILED,
-                                 TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
-                                 "exchange wire in different currency");
-          return GNUNET_SYSERR;
-        }
-        if (0 >
-            TALER_amount_add (&total_wire_fee,
-                              &total_wire_fee,
-                              &dc->wire_fee))
-        {
-          GNUNET_break (0);
-          resume_pay_with_error (pc,
-                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                 
TALER_EC_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
-                                 "could not add exchange wire fee to total");
-          return GNUNET_SYSERR;
-        }
-      }
+      /* Forward error, adding the "coin_pub" for which the
+         error was being generated */
+      if (TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS == hr->ec)
+        resume_pay_with_response (
+          pc,
+          MHD_HTTP_CONFLICT,
+          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
+                                    "hint",
+                                    "exchange failed on deposit of a coin",
+                                    "code",
+                                    (json_int_t) 
TALER_EC_PAY_INSUFFICIENT_FUNDS,
+                                    "exchange_code",
+                                    (json_int_t) hr->ec,
+                                    "exchange_http_status",
+                                    (json_int_t) hr->http_status,
+                                    "coin_pub",
+                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
+                                    "exchange_reply",
+                                    hr->reply));
+      else
+        resume_pay_with_response (
+          pc,
+          MHD_HTTP_FAILED_DEPENDENCY,
+          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
+                                    "hint",
+                                    "exchange failed on deposit of a coin",
+                                    "code",
+                                    (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+                                    "exchange_code",
+                                    (json_int_t) hr->ec,
+                                    "exchange_http_status",
+                                    (json_int_t) hr->http_status,
+                                    "coin_pub",
+                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
+                                    "exchange_reply",
+                                    hr->reply));
     }
+    return;
   }
 
+  /* store result to DB */
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Amount received from wallet: %s\n",
-              TALER_amount2s (&acc_amount));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Deposit fee for all coins: %s\n",
-              TALER_amount2s (&acc_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Total wire fee: %s\n",
-              TALER_amount2s (&total_wire_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Max wire fee: %s\n",
-              TALER_amount2s (&pc->max_wire_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Deposit fee limit for merchant: %s\n",
-              TALER_amount2s (&pc->max_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Total refunded amount: %s\n",
-              TALER_amount2s (&pc->total_refunded));
-
-  /* Now compare exchange wire fee compared to
-   * what we are willing to pay */
-  if (GNUNET_YES !=
-      TALER_amount_cmp_currency (&total_wire_fee,
-                                 &pc->max_wire_fee))
-  {
-    resume_pay_with_error (pc,
-                           MHD_HTTP_PRECONDITION_FAILED,
-                           TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
-                           "exchange wire does not match our currency");
-    return GNUNET_SYSERR;
-  }
-
-  switch (TALER_amount_subtract (&wire_fee_delta,
-                                 &total_wire_fee,
-                                 &pc->max_wire_fee))
-  {
-  case TALER_AAR_RESULT_POSITIVE:
-    /* Actual wire fee is indeed higher than our maximum,
-       compute how much the customer is expected to cover!  */
-    TALER_amount_divide (&wire_fee_customer_contribution,
-                         &wire_fee_delta,
-                         pc->wire_fee_amortization);
-    break;
-  case TALER_AAR_RESULT_ZERO:
-  case TALER_AAR_INVALID_NEGATIVE_RESULT:
-    /* Wire fee threshold is still above the wire fee amount.
-       Customer is not going to contribute on this.  */
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (total_wire_fee.currency,
-                                          &wire_fee_customer_contribution));
-    break;
-  default:
-    GNUNET_assert (0);
-  }
-
-  /* add wire fee contribution to the total fees */
-  if (0 >
-      TALER_amount_add (&acc_fee,
-                        &acc_fee,
-                        &wire_fee_customer_contribution))
-  {
-    GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                           TALER_EC_PAY_AMOUNT_OVERFLOW,
-                           "Overflow adding up amounts");
-    return GNUNET_SYSERR;
-  }
-  if (-1 == TALER_amount_cmp (&pc->max_fee,
-                              &acc_fee))
-  {
-    /**
-     * Sum of fees of *all* the different exchanges of all the coins are
-     * higher than the fixed limit that the merchant is willing to pay.  The
-     * difference must be paid by the customer.
-     *///
-    struct TALER_Amount excess_fee;
-
-    /* compute fee amount to be covered by customer */
-    GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
-                   TALER_amount_subtract (&excess_fee,
-                                          &acc_fee,
-                                          &pc->max_fee));
-    /* add that to the total */
-    if (0 >
-        TALER_amount_add (&total_needed,
-                          &excess_fee,
-                          &pc->amount))
-    {
-      GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_AMOUNT_OVERFLOW,
-                             "Overflow adding up amounts");
-      return GNUNET_SYSERR;
-    }
-  }
-  else
-  {
-    /* Fees are fully covered by the merchant, all we require
-       is that the total payment is not below the contract's amount */
-    total_needed = pc->amount;
-  }
-
-  /* Do not count refunds towards the payment */
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Subtracting total refunds from paid amount: %s\n",
-              TALER_amount2s (&pc->total_refunded));
-  if (0 >
-      TALER_amount_subtract (&final_amount,
-                             &acc_amount,
-                             &pc->total_refunded))
-  {
-    GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                           TALER_EC_PAY_REFUNDS_EXCEED_PAYMENTS,
-                           "refunded amount exceeds total payments");
-    return GNUNET_SYSERR;
-  }
-
-  if (-1 == TALER_amount_cmp (&final_amount,
-                              &total_needed))
-  {
-    /* acc_amount < total_needed */
-    if (-1 < TALER_amount_cmp (&acc_amount,
-                               &total_needed))
-    {
-      resume_pay_with_error (pc,
-                             MHD_HTTP_PAYMENT_REQUIRED,
-                             TALER_EC_PAY_REFUNDED,
-                             "contract not paid up due to refunds");
-    }
-    else if (-1 < TALER_amount_cmp (&acc_amount,
-                                    &pc->amount))
-    {
-      GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_NOT_ACCEPTABLE,
-                             TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
-                             "contract not paid up due to fees (client may 
have calculated them badly)");
-    }
-    else
-    {
-      GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_NOT_ACCEPTABLE,
-                             TALER_EC_PAY_PAYMENT_INSUFFICIENT,
-                             "payment insufficient");
-
-    }
-    return GNUNET_SYSERR;
-  }
-
-
-  return GNUNET_OK;
-}
-
-
-/**
- * Find the exchange we need to talk to for the next
- * pending deposit permission.
- *
- * @param pc payment context we are processing
- */
-static void
-find_next_exchange (struct PayContext *pc);
-
-
-/**
- * Begin of the DB transaction.  If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
- *
- * @param pc payment context to transact
- */
-static void
-begin_transaction (struct PayContext *pc);
-
-
-/**
- * Callback to handle a deposit permission's response.
- *
- * @param cls a `struct DepositConfirmation` (i.e. a pointer
- *   into the global array of confirmations and an index for this call
- *   in that array). That way, the last executed callback can detect
- *   that no other confirmations are on the way, and can pack a response
- *   for the wallet
- * @param hr HTTP response code details
- * @param exchange_sig signature from the exchange over the deposit 
confirmation
- * @param sign_key which key did the exchange use to sign the @a proof
- */
-static void
-deposit_cb (void *cls,
-            const struct TALER_EXCHANGE_HttpResponse *hr,
-            const struct TALER_ExchangeSignatureP *exchange_sig,
-            const struct TALER_ExchangePublicKeyP *sign_key)
-{
-  struct DepositConfirmation *dc = cls;
-  struct PayContext *pc = dc->pc;
-  enum GNUNET_DB_QueryStatus qs;
-
-  dc->dh = NULL;
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  pc->pending_at_ce--;
-  if (MHD_HTTP_OK != hr->http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Deposit operation failed with HTTP code %u/%d\n",
-                hr->http_status,
-                (int) hr->ec);
-    /* Transaction failed; stop all other ongoing deposits */
-    abort_deposit (pc);
-
-    if (5 == hr->http_status / 100)
-    {
-      /* internal server error at exchange */
-      resume_pay_with_response (pc,
-                                MHD_HTTP_SERVICE_UNAVAILABLE,
-                                TALER_MHD_make_json_pack (
-                                  "{s:s, s:I, s:I, s:I}",
-                                  "hint",
-                                  "exchange had an internal server error",
-                                  "code",
-                                  (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                  "exchange_code",
-                                  (json_int_t) hr->ec,
-                                  "exchange_http_status",
-                                  (json_int_t) hr->http_status));
-    }
-    else if (NULL == hr->reply)
-    {
-      /* We can't do anything meaningful here, the exchange did something 
wrong */
-      resume_pay_with_response (pc,
-                                MHD_HTTP_FAILED_DEPENDENCY,
-                                TALER_MHD_make_json_pack (
-                                  "{s:s, s:I, s:I, s:I}",
-                                  "hint",
-                                  "exchange failed, response body not even in 
JSON",
-                                  "code",
-                                  (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                  "exchange_code",
-                                  (json_int_t) hr->ec,
-                                  "exchange_http_status",
-                                  (json_int_t) hr->http_status));
-    }
-    else
-    {
-      /* Forward error, adding the "coin_pub" for which the
-         error was being generated */
-      if (TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS == hr->ec)
-        resume_pay_with_response (
-          pc,
-          MHD_HTTP_CONFLICT,
-          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
-                                    "hint",
-                                    "exchange failed on deposit of a coin",
-                                    "code",
-                                    (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                    "exchange_code",
-                                    (json_int_t) hr->ec,
-                                    "exchange_http_status",
-                                    (json_int_t) hr->http_status,
-                                    "coin_pub",
-                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
-                                    "exchange_reply",
-                                    hr->reply));
-      else
-        resume_pay_with_response (
-          pc,
-          MHD_HTTP_FAILED_DEPENDENCY,
-          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
-                                    "hint",
-                                    "exchange failed on deposit of a coin",
-                                    "code",
-                                    (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                    "exchange_code",
-                                    (json_int_t) hr->ec,
-                                    "exchange_http_status",
-                                    (json_int_t) hr->http_status,
-                                    "coin_pub",
-                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
-                                    "exchange_reply",
-                                    hr->reply));
-    }
-    return;
-  }
-  /* store result to DB */
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Storing successful payment for h_contract_terms `%s' and 
merchant `%s'\n",
+              "Storing successful payment %s (%s) at instance `%s'\n",
+              pc->hc->infix,
               GNUNET_h2s (&pc->h_contract_terms),
-              TALER_B2S (&pc->mi->pubkey));
-  /* NOTE: not run in any transaction block, simply as a
-     transaction by itself! */
-  db->preflight (db->cls);
-  qs = db->store_deposit (db->cls,
-                          &pc->h_contract_terms,
-                          &pc->mi->pubkey,
-                          &dc->coin_pub,
-                          dc->exchange_url,
-                          &dc->amount_with_fee,
-                          &dc->deposit_fee,
-                          &dc->refund_fee,
-                          &dc->wire_fee,
-                          sign_key,
-                          hr->reply);
+              pc->hc->instance->settings.id);
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->insert_deposit (TMH_db->cls,
+                               pc->hc->instance->settings.id,
+                               deposit_timestamp,
+                               &pc->h_contract_terms,
+                               &dc->coin_pub,
+                               dc->exchange_url,
+                               &dc->amount_with_fee,
+                               &dc->deposit_fee,
+                               &dc->refund_fee,
+                               &dc->wire_fee,
+                               &pc->wm->h_wire,
+                               exchange_sig,
+                               exchange_pub);
   if (0 > qs)
   {
     /* Special report if retries insufficient */
@@ -1065,7 +681,7 @@ deposit_cb (void *cls,
                            "Merchant database error");
     return;
   }
-  dc->found_in_db = GNUNET_YES;
+  dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */
   pc->pending--;
 
   if (0 != pc->pending_at_ce)
@@ -1079,22 +695,26 @@ deposit_cb (void *cls,
  *
  * @param cls the `struct PayContext`
  * @param hr HTTP response details
- * @param mh NULL if exchange was not found to be acceptable
- * @param wire_fee current applicable fee for dealing with @a mh,
+ * @param exchange_handle NULL if exchange was not found to be acceptable
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable fee for dealing with @a exchange_handle,
  *        NULL if not available
- * @param exchange_trusted #GNUNET_YES if this exchange is
+ * @param exchange_trusted true if this exchange is
  *        trusted by config
  */
 static void
 process_pay_with_exchange (void *cls,
                            const struct TALER_EXCHANGE_HttpResponse *hr,
-                           struct TALER_EXCHANGE_Handle *mh,
+                           struct TALER_EXCHANGE_Handle *exchange_handle,
+                           const char *payto_uri,
                            const struct TALER_Amount *wire_fee,
-                           int exchange_trusted)
+                           bool exchange_trusted)
 {
   struct PayContext *pc = cls;
+  struct TMH_HandlerContext *hc = pc->hc;
   const struct TALER_EXCHANGE_Keys *keys;
 
+  (void) payto_uri;
   pc->fo = NULL;
   GNUNET_assert (GNUNET_YES == pc->suspended);
   if (MHD_HTTP_OK != hr->http_status)
@@ -1120,8 +740,7 @@ process_pay_with_exchange (void *cls,
         hr->reply));
     return;
   }
-  pc->mh = mh;
-  keys = TALER_EXCHANGE_get_keys (mh);
+  keys = TALER_EXCHANGE_get_keys (exchange_handle);
   if (NULL == keys)
   {
     GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK 
*/
@@ -1132,12 +751,6 @@ process_pay_with_exchange (void *cls,
     return;
   }
 
-  GNUNET_log (
-    GNUNET_ERROR_TYPE_DEBUG,
-    "Found transaction data for proposal `%s' of merchant `%s', initiating 
deposits\n",
-    GNUNET_h2s (&pc->h_contract_terms),
-    TALER_B2S (&pc->mi->pubkey));
-
   /* Initiate /deposit operation for all coins of
      the current exchange (!) */
   GNUNET_assert (0 == pc->pending_at_ce);
@@ -1146,27 +759,26 @@ process_pay_with_exchange (void *cls,
     struct DepositConfirmation *dc = &pc->dc[i];
     const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
     enum TALER_ErrorCode ec;
-    unsigned int hc;
+    unsigned int http_status;
 
     if (NULL != dc->dh)
       continue; /* we were here before (can happen due to
                    tried_force_keys logic), don't go again */
-    if (GNUNET_YES == dc->found_in_db)
+    if (dc->found_in_db)
       continue;
     if (0 != strcmp (dc->exchange_url,
                      pc->current_exchange))
       continue;
-    denom_details = TALER_EXCHANGE_get_denomination_key (keys,
-                                                         &dc->denom);
+    denom_details
+      = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                     &dc->h_denom);
     if (NULL == denom_details)
     {
-      struct GNUNET_HashCode h_denom;
-
       if (! pc->tried_force_keys)
       {
         /* let's try *forcing* a re-download of /keys from the exchange.
            Maybe the wallet has seen /keys that we missed. */
-        pc->tried_force_keys = GNUNET_YES;
+        pc->tried_force_keys = true;
         pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
                                               pc->wm->wire_method,
                                               GNUNET_YES,
@@ -1176,8 +788,6 @@ process_pay_with_exchange (void *cls,
           return;
       }
       /* Forcing failed or we already did it, give up */
-      GNUNET_CRYPTO_rsa_public_key_hash (dc->denom.rsa_public_key,
-                                         &h_denom);
       resume_pay_with_response (
         pc,
         MHD_HTTP_FAILED_DEPENDENCY,
@@ -1185,49 +795,47 @@ process_pay_with_exchange (void *cls,
           "{s:s, s:I, s:o, s:o}",
           "hint", "coin's denomination not found",
           "code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
-          "h_denom_pub", GNUNET_JSON_from_data_auto (&h_denom),
-          "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh)));
+          "h_denom_pub", GNUNET_JSON_from_data_auto (&dc->h_denom),
+          "exchange_keys", TALER_EXCHANGE_get_keys_raw (exchange_handle)));
       return;
     }
     if (GNUNET_OK !=
-        TMH_AUDITORS_check_dk (mh,
+        TMH_AUDITORS_check_dk (exchange_handle,
                                denom_details,
                                exchange_trusted,
-                               &hc,
+                               &http_status,
                                &ec))
     {
       resume_pay_with_response (
         pc,
-        hc,
-        TALER_MHD_make_json_pack ("{s:s, s:I, s:o}",
-                                  "hint", "denomination not accepted",
-                                  "code", (json_int_t) ec,
-                                  "h_denom_pub", GNUNET_JSON_from_data_auto (
-                                    &denom_details->h_key)));
+        http_status,
+        TALER_MHD_make_json_pack (
+          "{s:s, s:I, s:o}",
+          "hint",
+          "denomination not accepted",
+          "code",
+          (json_int_t) ec,
+          "h_denom_pub",
+          GNUNET_JSON_from_data_auto (&denom_details->h_key)));
       return;
     }
 
     dc->deposit_fee = denom_details->fee_deposit;
     dc->refund_fee = denom_details->fee_refund;
     dc->wire_fee = *wire_fee;
-
     GNUNET_assert (NULL != pc->wm);
     GNUNET_assert (NULL != pc->wm->j_wire);
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Timing for this payment, wire_deadline: %llu, 
refund_deadline: %llu\n",
-                (unsigned long long) pc->wire_transfer_deadline.abs_value_us,
-                (unsigned long long) pc->refund_deadline.abs_value_us);
-    db->preflight (db->cls);
-    dc->dh = TALER_EXCHANGE_deposit (mh,
+    TMH_db->preflight (TMH_db->cls);
+    dc->dh = TALER_EXCHANGE_deposit (exchange_handle,
                                      &dc->amount_with_fee,
                                      pc->wire_transfer_deadline,
                                      pc->wm->j_wire,
                                      &pc->h_contract_terms,
                                      &dc->coin_pub,
                                      &dc->ub_sig,
-                                     &dc->denom,
+                                     &denom_details->key,
                                      pc->timestamp,
-                                     &pc->mi->pubkey,
+                                     &hc->instance->merchant_pub,
                                      pc->refund_deadline,
                                      &dc->coin_sig,
                                      &deposit_cb,
@@ -1240,7 +848,7 @@ process_pay_with_exchange (void *cls,
       GNUNET_break_op (0);
       resume_pay_with_response (
         pc,
-        MHD_HTTP_UNAUTHORIZED,
+        MHD_HTTP_FORBIDDEN,
         TALER_MHD_make_json_pack (
           "{s:s, s:I, s:i}",
           "hint", "deposit signature invalid",
@@ -1264,13 +872,13 @@ process_pay_with_exchange (void *cls,
 static void
 find_next_exchange (struct PayContext *pc)
 {
+  GNUNET_assert (0 == pc->pending_at_ce);
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
 
-    if (GNUNET_YES != dc->found_in_db)
+    if (! dc->found_in_db)
     {
-      db->preflight (db->cls);
       pc->current_exchange = dc->exchange_url;
       pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
                                             pc->wm->wire_method,
@@ -1290,509 +898,372 @@ find_next_exchange (struct PayContext *pc)
     }
   }
   pc->current_exchange = NULL;
-  db->preflight (db->cls);
   /* We are done with all the HTTP requests, go back and try
      the 'big' database transaction! (It should work now!) */
+  GNUNET_assert (0 == pc->pending);
   begin_transaction (pc);
 }
 
 
-/**
- * Handle a timeout for the processing of the pay request.
- *
- * @param cls our `struct PayContext`
- */
-static void
-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)
-  {
-    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
-    pc->fo = NULL;
-  }
-  resume_pay_with_error (pc,
-                         MHD_HTTP_REQUEST_TIMEOUT,
-                         TALER_EC_PAY_EXCHANGE_TIMEOUT,
-                         "likely the exchange did not reply quickly enough");
-}
-
-
 /**
  * Function called with information about a coin that was deposited.
  *
  * @param cls closure
- * @param h_contract_terms hashed proposal data
+ * @param exchange_url exchange where @a coin_pub was deposited
  * @param coin_pub public key of the coin
- * @param exchange_url URL of the exchange that issued @a coin_pub
  * @param amount_with_fee amount the exchange will deposit for this coin
  * @param deposit_fee fee the exchange will charge for this coin
  * @param refund_fee fee the exchange will charge for refunding this coin
  * @param wire_fee wire fee the exchange of this coin charges
- * @param exchange_proof proof from exchange that coin was accepted
  */
 static void
 check_coin_paid (void *cls,
-                 const struct GNUNET_HashCode *h_contract_terms,
-                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
                  const char *exchange_url,
+                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
                  const struct TALER_Amount *amount_with_fee,
                  const struct TALER_Amount *deposit_fee,
                  const struct TALER_Amount *refund_fee,
-                 const struct TALER_Amount *wire_fee,
-                 const json_t *exchange_proof)
+                 const struct TALER_Amount *wire_fee)
 {
   struct PayContext *pc = cls;
 
-  if (0 != GNUNET_memcmp (&pc->h_contract_terms,
-                          h_contract_terms))
-  {
-    GNUNET_break (0);
-    return;
-  }
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
 
-    if (GNUNET_YES == dc->found_in_db)
-      continue; /* processed earlier */
-
+    if (dc->found_in_db)
+      continue; /* processed earlier, skip "expensive" memcmp() */
     /* Get matching coin from results*/
     if ( (0 != GNUNET_memcmp (coin_pub,
                               &dc->coin_pub)) ||
+         (0 !=
+          strcmp (exchange_url,
+                  dc->exchange_url)) ||
          (0 != TALER_amount_cmp (amount_with_fee,
                                  &dc->amount_with_fee)) )
-      continue;
+      continue; /* does not match, skip */
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Coin (%s) already found in our DB.\n",
-                TALER_b2s (coin_pub,
-                           sizeof (*coin_pub)));
-    if (0 >
-        TALER_amount_add (&pc->total_paid,
-                          &pc->total_paid,
-                          amount_with_fee))
-    {
-      /* We accepted this coin for payment on this contract before,
-         and now we can't even add the amount!? */
-      GNUNET_break (0);
-      continue;
-    }
-    if (0 >
-        TALER_amount_add (&pc->total_fees_paid,
-                          &pc->total_fees_paid,
-                          deposit_fee))
-    {
-      /* We accepted this coin for payment on this contract before,
-         and now we can't even add the amount!? */
-      GNUNET_break (0);
-      continue;
-    }
+                "Deposit of coin `%s' already in our DB.\n",
+                TALER_B2S (coin_pub));
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&pc->total_paid,
+                                     &pc->total_paid,
+                                     amount_with_fee));
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&pc->total_fees_paid,
+                                     &pc->total_fees_paid,
+                                     deposit_fee));
     dc->deposit_fee = *deposit_fee;
     dc->refund_fee = *refund_fee;
     dc->wire_fee = *wire_fee;
     dc->amount_with_fee = *amount_with_fee;
-    dc->found_in_db = GNUNET_YES;
+    dc->found_in_db = true;
     pc->pending--;
   }
 }
 
 
 /**
- * Try to parse the pay request into the given pay context.
- * Schedules an error response in the connection on failure.
+ * Function called with information about a refund.  Check if this coin was
+ * claimed by the wallet for the transaction, and if so add the refunded
+ * amount to the pc's "total_refunded" amount.
  *
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- *         #GNUNET_NO on failure (response was queued with MHD)
- *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ * @param cls closure with a `struct PayContext`
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  */
-static enum GNUNET_GenericReturnValue
-parse_pay (struct MHD_Connection *connection,
-           const json_t *root,
-           struct PayContext *pc)
+static void
+check_coin_refunded (void *cls,
+                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                     const struct TALER_Amount *refund_amount)
 {
-  json_t *coins;
-  const char *order_id;
-  const char *mode;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  enum GNUNET_GenericReturnValue res;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_string ("mode",
-                             &mode),
-    GNUNET_JSON_spec_json ("coins",
-                           &coins),
-    GNUNET_JSON_spec_string ("order_id",
-                             &order_id),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
-                                 &merchant_pub),
-    GNUNET_JSON_spec_end ()
-  };
-  enum GNUNET_DB_QueryStatus qs;
+  struct PayContext *pc = cls;
 
-  res = TALER_MHD_parse_json_data (connection,
-                                   root,
-                                   spec);
-  if (GNUNET_YES != res)
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
-    GNUNET_break_op (0);
-    return res;
-  }
+    struct DepositConfirmation *dc = &pc->dc[i];
 
-  if (0 != GNUNET_memcmp (&merchant_pub,
-                          &pc->mi->pubkey))
-  {
-    GNUNET_JSON_parse_free (spec);
-    TALER_LOG_INFO (
-      "Unknown merchant public key included in payment (usually wrong instance 
chosen)\n");
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_BAD_REQUEST,
-                                   TALER_EC_PAY_WRONG_INSTANCE,
-                                   "merchant_pub in contract does not match 
this instance"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
+    /* Get matching coin from results*/
+    if (0 != GNUNET_memcmp (coin_pub,
+                            &dc->coin_pub))
+      continue;
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&pc->total_refunded,
+                                     &pc->total_refunded,
+                                     refund_amount));
   }
+}
 
-  {
-    const char *session_id;
 
-    session_id = json_string_value (json_object_get (root,
-                                                     "session_id"));
-    if (NULL != session_id)
-      pc->session_id = GNUNET_strdup (session_id);
-  }
-  GNUNET_assert (NULL == pc->order_id);
-  pc->order_id = GNUNET_strdup (order_id);
-  GNUNET_assert (NULL == pc->contract_terms);
-  qs = db->find_contract_terms (db->cls,
-                                &pc->contract_terms,
-                                order_id,
-                                &merchant_pub);
-  if (0 > qs)
-  {
-    GNUNET_JSON_parse_free (spec);
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                   TALER_EC_PAY_DB_FETCH_PAY_ERROR,
-                                   "Failed to obtain contract terms from DB"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    GNUNET_JSON_parse_free (spec);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_NOT_FOUND,
-                                   TALER_EC_PAY_PROPOSAL_NOT_FOUND,
-                                   "Proposal not found"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
+/**
+ * Check whether the amount paid is sufficient to cover the price.
+ *
+ * @param pc payment context to check
+ * @return true if the payment is sufficient, false if it is
+ *         insufficient
+ */
+static bool
+check_payment_sufficient (struct PayContext *pc)
+{
+  struct TALER_Amount acc_fee;
+  struct TALER_Amount acc_amount;
+  struct TALER_Amount final_amount;
+  struct TALER_Amount wire_fee_delta;
+  struct TALER_Amount wire_fee_customer_contribution;
+  struct TALER_Amount total_wire_fee;
+  struct TALER_Amount total_needed;
 
-  if (GNUNET_OK !=
-      TALER_JSON_hash (pc->contract_terms,
-                       &pc->h_contract_terms))
-  {
-    GNUNET_break (0);
-    GNUNET_JSON_parse_free (spec);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                   TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH,
-                                   "Failed to hash proposal"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Handling /pay for order `%s' with contract hash `%s'\n",
-              order_id,
-              GNUNET_h2s (&pc->h_contract_terms));
+  GNUNET_assert (0 != pc->coins_cnt);
+  acc_fee = pc->dc[0].deposit_fee;
+  total_wire_fee = pc->dc[0].wire_fee;
+  acc_amount = pc->dc[0].amount_with_fee;
 
-  if (NULL == json_object_get (pc->contract_terms,
-                               "merchant"))
-  {
-    /* invalid contract */
-    GNUNET_JSON_parse_free (spec);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                   TALER_EC_PAY_MERCHANT_FIELD_MISSING,
-                                   "No merchant field in proposal"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
-  if (0 != strcasecmp ("abort-refund",
-                       mode))
-    pc->mode = PC_MODE_PAY;
-  else
-    pc->mode = PC_MODE_ABORT_REFUND;
+  /**
+   * This loops calculates what are the deposit fee / total
+   * amount with fee / and wire fee, for all the coins.
+   */
+  for (unsigned int i = 1; i<pc->coins_cnt; i++)
   {
-    const char *fulfillment_url;
-    struct GNUNET_JSON_Specification espec[] = {
-      GNUNET_JSON_spec_absolute_time ("refund_deadline",
-                                      &pc->refund_deadline),
-      GNUNET_JSON_spec_absolute_time ("pay_deadline",
-                                      &pc->pay_deadline),
-      GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
-                                      &pc->wire_transfer_deadline),
-      GNUNET_JSON_spec_absolute_time ("timestamp",
-                                      &pc->timestamp),
-      TALER_JSON_spec_amount ("max_fee",
-                              &pc->max_fee),
-      TALER_JSON_spec_amount ("amount",
-                              &pc->amount),
-      GNUNET_JSON_spec_string ("fulfillment_url",
-                               &fulfillment_url),
-      GNUNET_JSON_spec_fixed_auto ("h_wire",
-                                   &pc->h_wire),
-      GNUNET_JSON_spec_end ()
-    };
+    struct DepositConfirmation *dc = &pc->dc[i];
 
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if (GNUNET_YES != res)
+    GNUNET_assert (dc->found_in_db);
+    if ( (0 >
+          TALER_amount_add (&acc_fee,
+                            &dc->deposit_fee,
+                            &acc_fee)) ||
+         (0 >
+          TALER_amount_add (&acc_amount,
+                            &dc->amount_with_fee,
+                            &acc_amount)) )
     {
       GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return res;
+      /* Overflow in these amounts? Very strange. */
+      resume_pay_with_error (pc,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_PAY_AMOUNT_OVERFLOW,
+                             "Overflow adding up amounts");
     }
-
-    pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
-    if (pc->wire_transfer_deadline.abs_value_us <
-        pc->refund_deadline.abs_value_us)
+    if (1 ==
+        TALER_amount_cmp (&dc->deposit_fee,
+                          &dc->amount_with_fee))
     {
-      /* This should already have been checked when creating the
-         order! */
-      GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
-                                         "refund deadline after wire transfer 
deadline");
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             MHD_HTTP_BAD_REQUEST,
+                             TALER_EC_PAY_FEES_EXCEED_PAYMENT,
+                             "Deposit fees exceed coin's contribution");
+      return false;
     }
 
-    if (pc->pay_deadline.abs_value_us <
-        GNUNET_TIME_absolute_get ().abs_value_us)
+    /* If exchange differs, add wire fee */
     {
-      /* too late */
-      GNUNET_JSON_parse_free (spec);
-      return
-        (MHD_YES ==
-         TALER_MHD_reply_with_error (connection,
-                                     MHD_HTTP_GONE,
-                                     TALER_EC_PAY_OFFER_EXPIRED,
-                                     "The payment deadline has past and the 
offer is no longer valid"))
-        ? GNUNET_NO
-        : GNUNET_SYSERR;
-    }
+      bool new_exchange = true;
 
-  }
-
-  /* find wire method */
-  {
-    struct WireMethod *wm;
-
-    wm = pc->mi->wm_head;
-    while (0 != GNUNET_memcmp (&pc->h_wire,
-                               &wm->h_wire))
-      wm = wm->next;
-    if (NULL == wm)
-    {
-      GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_PAY_WIRE_HASH_UNKNOWN,
-                                         "Did not find matching wire details");
+      for (unsigned int j = 0; j<i; j++)
+        if (0 == strcasecmp (dc->exchange_url,
+                             pc->dc[j].exchange_url))
+        {
+          new_exchange = false;
+          break;
+        }
+      if (new_exchange)
+      {
+        if (GNUNET_OK !=
+            TALER_amount_cmp_currency (&total_wire_fee,
+                                       &dc->wire_fee))
+        {
+          GNUNET_break_op (0);
+          resume_pay_with_error (pc,
+                                 MHD_HTTP_PRECONDITION_FAILED,
+                                 TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
+                                 "exchange wire in different currency");
+          return false;
+        }
+        if (0 >
+            TALER_amount_add (&total_wire_fee,
+                              &total_wire_fee,
+                              &dc->wire_fee))
+        {
+          GNUNET_break (0);
+          resume_pay_with_error (pc,
+                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                 
TALER_EC_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
+                                 "could not add exchange wire fee to total");
+          return false;
+        }
+      }
     }
-    pc->wm = wm;
   }
 
-  /* parse optional details */
-  if (NULL != json_object_get (pc->contract_terms,
-                               "max_wire_fee"))
-  {
-    struct GNUNET_JSON_Specification espec[] = {
-      TALER_JSON_spec_amount ("max_wire_fee",
-                              &pc->max_wire_fee),
-      GNUNET_JSON_spec_end ()
-    };
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Amount received from wallet: %s\n",
+              TALER_amount2s (&acc_amount));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Deposit fee for all coins: %s\n",
+              TALER_amount2s (&acc_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Total wire fee: %s\n",
+              TALER_amount2s (&total_wire_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Max wire fee: %s\n",
+              TALER_amount2s (&pc->max_wire_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Deposit fee limit for merchant: %s\n",
+              TALER_amount2s (&pc->max_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Total refunded amount: %s\n",
+              TALER_amount2s (&pc->total_refunded));
 
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if (GNUNET_YES != res)
-    {
-      GNUNET_break_op (0); /* invalid input, fail */
-      GNUNET_JSON_parse_free (spec);
-      return res;
-    }
+  /* Now compare exchange wire fee compared to
+   * what we are willing to pay */
+  if (GNUNET_YES !=
+      TALER_amount_cmp_currency (&total_wire_fee,
+                                 &pc->max_wire_fee))
+  {
+    resume_pay_with_error (pc,
+                           MHD_HTTP_PRECONDITION_FAILED,
+                           TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
+                           "exchange wire does not match our currency");
+    return false;
   }
-  else
+
+  switch (TALER_amount_subtract (&wire_fee_delta,
+                                 &total_wire_fee,
+                                 &pc->max_wire_fee))
   {
-    /* default is we cover no fee */
+  case TALER_AAR_RESULT_POSITIVE:
+    /* Actual wire fee is indeed higher than our maximum,
+       compute how much the customer is expected to cover!  */
+    TALER_amount_divide (&wire_fee_customer_contribution,
+                         &wire_fee_delta,
+                         pc->wire_fee_amortization);
+    break;
+  case TALER_AAR_RESULT_ZERO:
+  case TALER_AAR_INVALID_NEGATIVE_RESULT:
+    /* Wire fee threshold is still above the wire fee amount.
+       Customer is not going to contribute on this.  */
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (pc->max_fee.currency,
-                                          &pc->max_wire_fee));
+                   TALER_amount_get_zero (total_wire_fee.currency,
+                                          &wire_fee_customer_contribution));
+    break;
+  default:
+    GNUNET_assert (0);
   }
 
-  if (NULL != json_object_get (pc->contract_terms,
-                               "wire_fee_amortization"))
+  /* add wire fee contribution to the total fees */
+  if (0 >
+      TALER_amount_add (&acc_fee,
+                        &acc_fee,
+                        &wire_fee_customer_contribution))
   {
-    struct GNUNET_JSON_Specification espec[] = {
-      GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
-                               &pc->wire_fee_amortization),
-      GNUNET_JSON_spec_end ()
-    };
+    GNUNET_break (0);
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_AMOUNT_OVERFLOW,
+                           "Overflow adding up amounts");
+    return false;
+  }
+  if (-1 == TALER_amount_cmp (&pc->max_fee,
+                              &acc_fee))
+  {
+    /**
+     * Sum of fees of *all* the different exchanges of all the coins are
+     * higher than the fixed limit that the merchant is willing to pay.  The
+     * difference must be paid by the customer.
+     *///
+    struct TALER_Amount excess_fee;
 
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if ( (GNUNET_YES != res) ||
-         (0 == pc->wire_fee_amortization) )
+    /* compute fee amount to be covered by customer */
+    GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
+                   TALER_amount_subtract (&excess_fee,
+                                          &acc_fee,
+                                          &pc->max_fee));
+    /* add that to the total */
+    if (0 >
+        TALER_amount_add (&total_needed,
+                          &excess_fee,
+                          &pc->amount))
     {
-      GNUNET_break_op (0); /* invalid input, use default */
-      /* default is no amortization */
-      pc->wire_fee_amortization = 1;
+      GNUNET_break (0);
+      resume_pay_with_error (pc,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_PAY_AMOUNT_OVERFLOW,
+                             "Overflow adding up amounts");
+      return false;
     }
   }
   else
   {
-    pc->wire_fee_amortization = 1;
+    /* Fees are fully covered by the merchant, all we require
+       is that the total payment is not below the contract's amount */
+    total_needed = pc->amount;
   }
 
-  pc->coins_cnt = json_array_size (coins);
-  if (0 == pc->coins_cnt)
+  /* Do not count refunds towards the payment */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Subtracting total refunds from paid amount: %s\n",
+              TALER_amount2s (&pc->total_refunded));
+  if (0 >
+      TALER_amount_subtract (&final_amount,
+                             &acc_amount,
+                             &pc->total_refunded))
   {
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PAY_COINS_ARRAY_EMPTY,
-                                       "coins");
+    GNUNET_break (0);
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_REFUNDS_EXCEED_PAYMENTS,
+                           "refunded amount exceeds total payments");
+    return false;
   }
-  /* note: 1 coin = 1 deposit confirmation expected */
-  pc->dc = GNUNET_new_array (pc->coins_cnt,
-                             struct DepositConfirmation);
 
-  /* This loop populates the array 'dc' in 'pc' */
+  if (-1 == TALER_amount_cmp (&final_amount,
+                              &total_needed))
   {
-    unsigned int coins_index;
-    json_t *coin;
-    json_array_foreach (coins, coins_index, coin)
+    /* acc_amount < total_needed */
+    if (-1 < TALER_amount_cmp (&acc_amount,
+                               &total_needed))
     {
-      struct DepositConfirmation *dc = &pc->dc[coins_index];
-      const char *exchange_url;
-      struct GNUNET_JSON_Specification ispec[] = {
-        TALER_JSON_spec_denomination_public_key ("denom_pub",
-                                                 &dc->denom),
-        TALER_JSON_spec_amount ("contribution",
-                                &dc->amount_with_fee),
-        GNUNET_JSON_spec_string ("exchange_url",
-                                 &exchange_url),
-        GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                     &dc->coin_pub),
-        TALER_JSON_spec_denomination_signature ("ub_sig",
-                                                &dc->ub_sig),
-        GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                     &dc->coin_sig),
-        GNUNET_JSON_spec_end ()
-      };
-
-      res = TALER_MHD_parse_json_data (connection,
-                                       coin,
-                                       ispec);
-      if (GNUNET_YES != res)
-      {
-        GNUNET_JSON_parse_free (spec);
-        GNUNET_break_op (0);
-        return res;
-      }
-      dc->exchange_url = GNUNET_strdup (exchange_url);
-      dc->index = coins_index;
-      dc->pc = pc;
+      resume_pay_with_error (pc,
+                             MHD_HTTP_PAYMENT_REQUIRED,
+                             TALER_EC_PAY_REFUNDED,
+                             "contract not paid up due to refunds");
     }
-  }
-  pc->pending = pc->coins_cnt;
-  GNUNET_JSON_parse_free (spec);
-  return GNUNET_OK;
-}
-
-
-/**
- * Function called with information about a refund.
- * Check if this coin was claimed by the wallet for the
- * transaction, and if so add the refunded amount to the
- * pc's "total_refunded" amount.
- *
- * @param cls closure with a `struct PayContext`
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
- */
-static void
-check_coin_refunded (void *cls,
-                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                     const char *exchange_url,
-                     uint64_t rtransaction_id,
-                     const char *reason,
-                     const struct TALER_Amount *refund_amount,
-                     const struct TALER_Amount *refund_fee)
-{
-  struct PayContext *pc = cls;
-
-  (void) exchange_url;
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
-  {
-    struct DepositConfirmation *dc = &pc->dc[i];
-
-    /* Get matching coin from results*/
-    if (0 == GNUNET_memcmp (coin_pub,
-                            &dc->coin_pub))
+    else if (-1 < TALER_amount_cmp (&acc_amount,
+                                    &pc->amount))
+    {
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             MHD_HTTP_NOT_ACCEPTABLE,
+                             TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
+                             "contract not paid up due to fees (client may 
have calculated them badly)");
+    }
+    else
     {
-      dc->refunded = GNUNET_YES;
-      GNUNET_assert (0 <=
-                     TALER_amount_add (&pc->total_refunded,
-                                       &pc->total_refunded,
-                                       refund_amount));
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             MHD_HTTP_NOT_ACCEPTABLE,
+                             TALER_EC_PAY_PAYMENT_INSUFFICIENT,
+                             "payment insufficient");
     }
+    return false;
   }
+  return true;
 }
 
 
 /**
- * Begin of the DB transaction.  If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Begin of the DB transaction for a payment.  If required (from
+ * soft/serialization errors), the transaction can be restarted here.
  *
- * @param pc payment context to transact
+ * @param[in,out] pc payment context to transact
  */
 static void
 begin_transaction (struct PayContext *pc)
 {
   enum GNUNET_DB_QueryStatus qs;
+  struct TMH_HandlerContext *hc = pc->hc;
+  const char *instance_id = hc->instance->settings.id;
+  bool refunded;
 
   /* Avoid re-trying transactions on soft errors forever! */
   if (pc->retry_counter++ > MAX_RETRIES)
@@ -1806,7 +1277,9 @@ begin_transaction (struct PayContext *pc)
   }
   GNUNET_assert (GNUNET_YES == pc->suspended);
 
-  /* Init. some price accumulators.  */
+  /* Initialize some amount accumulators
+     (used in check_coin_paid(), check_coin_refunded()
+     and check_payment_sufficient()). */
   GNUNET_break (GNUNET_OK ==
                 TALER_amount_get_zero (pc->amount.currency,
                                        &pc->total_paid));
@@ -1816,12 +1289,15 @@ begin_transaction (struct PayContext *pc)
   GNUNET_break (GNUNET_OK ==
                 TALER_amount_get_zero (pc->amount.currency,
                                        &pc->total_refunded));
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+    pc->dc[i].found_in_db = false;
+  pc->pending = pc->coins_cnt;
 
   /* First, try to see if we have all we need already done */
-  db->preflight (db->cls);
+  TMH_db->preflight (TMH_db->cls);
   if (GNUNET_OK !=
-      db->start (db->cls,
-                 "run pay"))
+      TMH_db->start (TMH_db->cls,
+                     "run pay"))
   {
     GNUNET_break (0);
     resume_pay_with_error (pc,
@@ -1832,14 +1308,14 @@ begin_transaction (struct PayContext *pc)
   }
 
   /* Check if some of these coins already succeeded for _this_ contract.  */
-  qs = db->find_payments (db->cls,
-                          &pc->h_contract_terms,
-                          &pc->mi->pubkey,
-                          &check_coin_paid,
-                          pc);
+  qs = TMH_db->lookup_deposits (TMH_db->cls,
+                                instance_id,
+                                &pc->h_contract_terms,
+                                &check_coin_paid,
+                                pc);
   if (0 > qs)
   {
-    db->rollback (db->cls);
+    TMH_db->rollback (TMH_db->cls);
     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
       begin_transaction (pc);
@@ -1855,14 +1331,14 @@ begin_transaction (struct PayContext *pc)
   }
 
   /* Check if we refunded some of the coins */
-  qs = db->get_refunds_from_contract_terms_hash (db->cls,
-                                                 &pc->mi->pubkey,
-                                                 &pc->h_contract_terms,
-                                                 &check_coin_refunded,
-                                                 pc);
+  qs = TMH_db->lookup_refunds (TMH_db->cls,
+                               instance_id,
+                               &pc->h_contract_terms,
+                               &check_coin_refunded,
+                               pc);
   if (0 > qs)
   {
-    db->rollback (db->cls);
+    TMH_db->rollback (TMH_db->cls);
     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
       begin_transaction (pc);
@@ -1876,340 +1352,473 @@ begin_transaction (struct PayContext *pc)
                            "Merchant database error checking for refunds");
     return;
   }
+  refunded = (qs > 0);
 
-  /* All the coins known to the database have
-   * been processed, now delve into specific case
-   * (pay vs. abort) */
+  /* Check if there are coins that still need to be processed */
 
-  if (PC_MODE_ABORT_REFUND == pc->mode)
+  if (0 != pc->pending)
   {
-    json_t *terms;
+    /* we made no DB changes, so we can just rollback */
+    TMH_db->rollback (TMH_db->cls);
 
-    /* The wallet is going for a refund,
-       (on aborted operation)! */
+    /* Ok, we need to first go to the network to process more coins.
+       We that interaction in *tiny* transactions (hence the rollback
+       above). */
+    find_next_exchange (pc);
+    return;
+  }
 
-    /* check payment was indeed incomplete */
-    qs = db->find_paid_contract_terms_from_hash (db->cls,
-                                                 &terms,
-                                                 &pc->h_contract_terms,
-                                                 &pc->mi->pubkey);
-    if (0 > qs)
-    {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                             "Merchant database error");
-      return;
-    }
-    if (0 < qs)
+  /* 0 == pc->pending: all coins processed, let's see if that was enough */
+  if (! check_payment_sufficient (pc))
+  {
+    /* check_payment_sufficient() will have queued an error already.
+       We need to still abort the transaction. */
+    TMH_db->rollback (TMH_db->cls);
+    return;
+  }
+  /* Payment succeeded, save in database */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Order `%s' (%s) was fully paid\n",
+              pc->order_id,
+              GNUNET_h2s (&pc->h_contract_terms));
+  qs = TMH_db->mark_contract_paid (TMH_db->cls,
+                                   instance_id,
+                                   &pc->h_contract_terms,
+                                   pc->session_id);
+  if (qs < 0)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
-      /* Payment had been complete! */
-      json_decref (terms);
-      db->rollback (db->cls);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_FORBIDDEN,
-                             
TALER_EC_PAY_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
-                             "Payment complete, refusing to abort");
+      begin_transaction (pc);
       return;
     }
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+                           "Could not set contract to 'paid' in DB");
+    return;
+  }
 
-    /* Store refund in DB */
-    qs = db->increase_refund_for_contract_NT (db->cls,
-                                              &pc->h_contract_terms,
-                                              &pc->mi->pubkey,
-                                              &pc->total_paid,
-                                              /* justification */
-                                              "incomplete payment aborted");
-    if (0 > qs)
+  /* Now commit! */
+  if (0 <= qs)
+    qs = TMH_db->commit (TMH_db->cls);
+  else
+    TMH_db->rollback (TMH_db->cls);
+  if (0 > qs)
+  {
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                             "Merchant database error storing abort-refund");
+      begin_transaction (pc);
       return;
     }
-    qs = db->commit (db->cls);
-    if (0 > qs)
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+                           "Database commit mark proposal as 'paid' failed");
+    return;
+  }
+
+  /* Notify clients that have been waiting for the payment to succeed */
+  TMH_long_poll_resume (pc->order_id,
+                        hc->instance,
+                        NULL);
+  TMH_notify_order_change (hc->instance,
+                           pc->order_id,
+                           true, /* paid */
+                           refunded,
+                           false, /* wired */
+                           pc->timestamp,
+                           pc->order_serial);
+
+  /* Generate response (payment successful) */
+  {
+    struct GNUNET_CRYPTO_EddsaSignature sig;
+
+    /* Sign on our end (as the payment did go through, even if it may
+       have been refunded already) */
     {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                             "Merchant database error: could not commit");
-      return;
+      struct PaymentResponsePS mr = {
+        .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+        .purpose.size = htonl (sizeof (mr)),
+        .h_contract_terms = pc->h_contract_terms
+      };
+
+      GNUNET_CRYPTO_eddsa_sign (&pc->hc->instance->merchant_priv.eddsa_priv,
+                                &mr,
+                                &sig);
     }
-    /* At this point, the refund got correctly committed
-     * into the database.  */
+
+    /* Build the response */
     {
-      json_t *refunds;
+      json_t *resp;
 
-      refunds = json_array ();
-      if (NULL == refunds)
+      resp = json_pack ("{s:o}",
+                        "sig",
+                        GNUNET_JSON_from_data_auto (&sig));
+      if (NULL == resp)
       {
         GNUNET_break (0);
         resume_pay_with_error (pc,
                                MHD_HTTP_INTERNAL_SERVER_ERROR,
                                TALER_EC_JSON_ALLOCATION_FAILURE,
-                               "could not create JSON array");
+                               "Could not build final response");
         return;
       }
-      for (unsigned int i = 0; i<pc->coins_cnt; i++)
+      resume_pay_with_response (pc,
+                                MHD_HTTP_OK,
+                                TALER_MHD_make_json (resp));
+      json_decref (resp);
+    }
+  }
+}
+
+
+/**
+ * Try to parse the pay request into the given pay context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param connection HTTP connection we are receiving payment on
+ * @param[in,out] hc context with further information about the request
+ * @param pc context we use to handle the payment
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO on failure (response was queued with MHD)
+ *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ */
+static enum GNUNET_GenericReturnValue
+parse_pay (struct MHD_Connection *connection,
+           struct TMH_HandlerContext *hc,
+           struct PayContext *pc)
+{
+  /* First, parse request */
+  {
+    json_t *coins;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_json ("coins",
+                             &coins),
+      GNUNET_JSON_spec_end ()
+    };
+
+    {
+      enum GNUNET_GenericReturnValue res;
+
+      res = TALER_MHD_parse_json_data (connection,
+                                       hc->request_body,
+                                       spec);
+      if (GNUNET_YES != res)
+      {
+        GNUNET_break_op (0);
+        return res;
+      }
+    }
+
+    if ( (! json_is_array (coins)) ||
+         (0 == (pc->coins_cnt = json_array_size (coins))) )
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_PAY_COINS_ARRAY_EMPTY,
+                                         "'coins' array is empty or not even 
an array");
+    }
+
+    /* note: 1 coin = 1 deposit confirmation expected */
+    pc->dc = GNUNET_new_array (pc->coins_cnt,
+                               struct DepositConfirmation);
+
+    /* This loop populates the array 'dc' in 'pc' */
+    {
+      unsigned int coins_index;
+      json_t *coin;
+      json_array_foreach (coins, coins_index, coin)
       {
-        struct TALER_MerchantSignatureP msig;
-        struct TALER_RefundRequestPS rr = {
-          .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
-          .purpose.size = htonl (sizeof (rr)),
-          .h_contract_terms = pc->h_contract_terms,
-          .coin_pub = pc->dc[i].coin_pub,
-          .merchant = pc->mi->pubkey,
-          .rtransaction_id = GNUNET_htonll (0)
+        struct DepositConfirmation *dc = &pc->dc[coins_index];
+        const char *exchange_url;
+        enum GNUNET_GenericReturnValue res;
+        struct GNUNET_JSON_Specification ispec[] = {
+          GNUNET_JSON_spec_fixed_auto ("h_denom",
+                                       &dc->h_denom),
+          TALER_JSON_spec_amount ("contribution",
+                                  &dc->amount_with_fee),
+          GNUNET_JSON_spec_string ("exchange_url",
+                                   &exchange_url),
+          GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                       &dc->coin_pub),
+          TALER_JSON_spec_denomination_signature ("ub_sig",
+                                                  &dc->ub_sig),
+          GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                       &dc->coin_sig),
+          GNUNET_JSON_spec_end ()
         };
 
-        if (GNUNET_YES != pc->dc[i].found_in_db)
-          continue; /* Skip coins not found in DB.  */
-        TALER_amount_hton (&rr.refund_amount,
-                           &pc->dc[i].amount_with_fee);
-        TALER_amount_hton (&rr.refund_fee,
-                           &pc->dc[i].refund_fee);
-
-        GNUNET_CRYPTO_eddsa_sign (&pc->mi->privkey.eddsa_priv,
-                                  &rr,
-                                  &msig.eddsa_sig);
-        /* Pack refund for i-th coin.  */
-        if (0 !=
-            json_array_append_new (
-              refunds,
-              json_pack ("{s:I, s:o, s:o s:o s:o}",
-                         "rtransaction_id",
-                         (json_int_t) 0,
-                         "coin_pub",
-                         GNUNET_JSON_from_data_auto (&rr.coin_pub),
-                         "merchant_sig",
-                         GNUNET_JSON_from_data_auto (&msig),
-                         "refund_amount",
-                         TALER_JSON_from_amount_nbo (&rr.refund_amount),
-                         "refund_fee",
-                         TALER_JSON_from_amount_nbo (&rr.refund_fee))))
+        res = TALER_MHD_parse_json_data (connection,
+                                         coin,
+                                         ispec);
+        if (GNUNET_YES != res)
         {
-          json_decref (refunds);
-          GNUNET_break (0);
-          resume_pay_with_error (pc,
-                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                 TALER_EC_JSON_ALLOCATION_FAILURE,
-                                 "could not create JSON array");
-          return;
+          GNUNET_JSON_parse_free (spec);
+          GNUNET_break_op (0);
+          return res;
         }
+        dc->exchange_url = GNUNET_strdup (exchange_url);
+        dc->index = coins_index;
+        dc->pc = pc;
       }
+    }
+    GNUNET_JSON_parse_free (spec);
+  }
 
-      /* Resume and send back the response.  */
-      resume_pay_with_response (
-        pc,
-        MHD_HTTP_OK,
-        TALER_MHD_make_json_pack (
-          "{s:o, s:o, s:o}",
-          /* Refunds pack.  */
-          "refund_permissions", refunds,
-          "merchant_pub",
-          GNUNET_JSON_from_data_auto (&pc->mi->pubkey),
-          "h_contract_terms",
-          GNUNET_JSON_from_data_auto (&pc->h_contract_terms)));
+  /* copy session ID (if set) */
+  {
+    const char *session_id;
+    json_t *sid;
+
+    sid = json_object_get (hc->request_body,
+                           "session_id");
+    if (NULL != sid)
+    {
+      if (! json_is_string (sid))
+      {
+        GNUNET_break_op (0);
+        return (MHD_YES ==
+                TALER_MHD_reply_with_error (connection,
+                                            MHD_HTTP_BAD_REQUEST,
+                                            TALER_EC_PARAMETER_MALFORMED,
+                                            "session_id"))
+               ? GNUNET_NO
+               : GNUNET_SYSERR;
+      }
+      session_id = json_string_value (sid);
+      GNUNET_assert (NULL != session_id);
+      pc->session_id = GNUNET_strdup (session_id);
     }
-    return;
-  } /* End of PC_MODE_ABORT_REFUND */
+  }
+
+  /* copy order ID */
+  {
+    const char *order_id = hc->infix;
 
-  /* Default PC_MODE_PAY mode */
+    GNUNET_assert (NULL != order_id);
+    GNUNET_assert (NULL == pc->order_id);
+    pc->order_id = GNUNET_strdup (order_id);
+  }
 
-  /* Final termination case: all coins already known, just
-     generate ultimate outcome. */
-  if (0 == pc->pending)
+  /* obtain contract terms */
   {
-    if (GNUNET_OK != check_payment_sufficient (pc))
+    enum GNUNET_DB_QueryStatus qs;
+    json_t *contract_terms = NULL;
+
+    qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                        hc->instance->settings.id,
+                                        pc->order_id,
+                                        &contract_terms,
+                                        &pc->order_serial);
+    if (0 > qs)
     {
-      db->rollback (db->cls);
-      return;
+      /* single, read-only SQL statements should never cause
+         serialization problems */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                          TALER_EC_PAY_DB_FETCH_PAY_ERROR,
+                                          "Failed to obtain contract terms 
from DB"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_NOT_FOUND,
+                                          TALER_EC_PAY_PROPOSAL_NOT_FOUND,
+                                          "Proposal not found"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
+    }
+
+    /* hash contract (needed later) */
+    if (GNUNET_OK !=
+        TALER_JSON_hash (contract_terms,
+                         &pc->h_contract_terms))
+    {
+      GNUNET_break (0);
+      json_decref (contract_terms);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                          
TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH,
+                                          "Failed to hash contract terms"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
     }
-    /* Payment succeeded, save in database */
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Contract `%s' was fully paid\n",
+                "Handling payment for order `%s' with contract hash `%s'\n",
+                pc->order_id,
                 GNUNET_h2s (&pc->h_contract_terms));
-    qs = db->mark_proposal_paid (db->cls,
-                                 &pc->h_contract_terms,
-                                 &pc->mi->pubkey);
-    if (qs < 0)
+
+    /* basic sanity check on the contract */
+    if (NULL == json_object_get (contract_terms,
+                                 "merchant"))
     {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      /* invalid contract */
+      GNUNET_break (0);
+      json_decref (contract_terms);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                          TALER_EC_PAY_MERCHANT_FIELD_MISSING,
+                                          "No merchant field in proposal"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
+    }
+
+    /* Get details from contract and check fundamentals */
+    {
+      struct GNUNET_JSON_Specification espec[] = {
+        GNUNET_JSON_spec_absolute_time ("refund_deadline",
+                                        &pc->refund_deadline),
+        GNUNET_JSON_spec_absolute_time ("pay_deadline",
+                                        &pc->pay_deadline),
+        GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
+                                        &pc->wire_transfer_deadline),
+        GNUNET_JSON_spec_absolute_time ("timestamp",
+                                        &pc->timestamp),
+        TALER_JSON_spec_amount ("max_fee",
+                                &pc->max_fee),
+        TALER_JSON_spec_amount ("amount",
+                                &pc->amount),
+        GNUNET_JSON_spec_fixed_auto ("h_wire",
+                                     &pc->h_wire),
+        GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
+                                 &pc->wire_fee_amortization),
+        TALER_JSON_spec_amount ("max_wire_fee",
+                                &pc->max_wire_fee),
+        GNUNET_JSON_spec_end ()
+      };
+      enum GNUNET_GenericReturnValue res;
+
+      res = TALER_MHD_parse_internal_json_data (connection,
+                                                contract_terms,
+                                                espec);
+      json_decref (contract_terms);
+      if (GNUNET_YES != res)
       {
-        begin_transaction (pc);
-        return;
+        GNUNET_break (0);
+        return res;
       }
-      resume_pay_with_error (
-        pc,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
-        "Merchant database error: could not mark proposal as 'paid'");
-      return;
     }
 
-    if ( (NULL != pc->session_id) &&
-         (NULL != pc->fulfillment_url) )
+    if (pc->wire_transfer_deadline.abs_value_us <
+        pc->refund_deadline.abs_value_us)
     {
-      qs = db->insert_session_info (db->cls,
-                                    pc->session_id,
-                                    pc->fulfillment_url,
-                                    pc->order_id,
-                                    &pc->mi->pubkey);
+      /* This should already have been checked when creating the order! */
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
+                                         "refund deadline after wire transfer 
deadline");
     }
 
-    /* Now commit! */
-    if (0 <= qs)
-      qs = db->commit (db->cls);
-    else
-      db->rollback (db->cls);
-    if (0 > qs)
+    if (pc->pay_deadline.abs_value_us <
+        GNUNET_TIME_absolute_get ().abs_value_us)
     {
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      resume_pay_with_error (
-        pc,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
-        "Merchant database error: could not commit to mark proposal as 
'paid'");
-      return;
+      /* too late */
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_GONE,
+                                          TALER_EC_PAY_OFFER_EXPIRED,
+                                          "We are past the payment deadline"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
     }
-    TMH_long_poll_resume (pc->order_id,
-                          &pc->mi->pubkey,
-                          NULL);
-    generate_success_response (pc);
-    return;
   }
 
+  /* Make sure wire method (still) exists for this instance */
+  {
+    struct TMH_WireMethod *wm;
 
-  /* we made no DB changes,
-     so we can just rollback */
-  db->rollback (db->cls);
+    wm = hc->instance->wm_head;
+    while (0 != GNUNET_memcmp (&pc->h_wire,
+                               &wm->h_wire))
+      wm = wm->next;
+    if (NULL == wm)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_PAY_WIRE_HASH_UNKNOWN,
+                                         "Did not find matching wire details");
+    }
+    pc->wm = wm;
+  }
 
-  /* Ok, we need to first go to the network.
-     Do that interaction in *tiny* transactions. */
-  find_next_exchange (pc);
+  return GNUNET_OK;
 }
 
 
 /**
- * Process a payment for a proposal.
+ * Handle a timeout for the processing of the pay request.
  *
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return value to return to MHD (#MHD_NO to drop connection,
- *         #MHD_YES to keep handling it)
+ * @param cls our `struct PayContext`
  */
-static MHD_RESULT
-handler_pay_json (struct MHD_Connection *connection,
-                  const json_t *root,
-                  struct PayContext *pc)
+static void
+handle_pay_timeout (void *cls)
 {
-  {
-    enum GNUNET_GenericReturnValue ret;
-
-    ret = parse_pay (connection,
-                     root,
-                     pc);
-    if (GNUNET_OK != ret)
-      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
-  }
+  struct PayContext *pc = cls;
 
-  /* Payment not finished, suspend while we interact with the exchange */
-  MHD_suspend_connection (connection);
-  pc->suspended = GNUNET_YES;
+  pc->timeout_task = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Suspending /pay handling while working with the exchange\n");
-  pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
-                                                   &handle_pay_timeout,
-                                                   pc);
-  begin_transaction (pc);
-  return MHD_YES;
+              "Resuming pay with error after timeout\n");
+  if (NULL != pc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
+    pc->fo = NULL;
+  }
+  resume_pay_with_error (pc,
+                         MHD_HTTP_REQUEST_TIMEOUT,
+                         TALER_EC_PAY_EXCHANGE_TIMEOUT,
+                         "likely the exchange did not reply quickly enough");
 }
 
 
 /**
- * Process a payment for a proposal.  Takes data from the given MHD
- * connection.
+ * Process a payment for a claimed order.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_pay (struct TMH_RequestHandler *rh,
-                struct MHD_Connection *connection,
-                void **connection_cls,
-                const char *upload_data,
-                size_t *upload_data_size,
-                struct MerchantInstance *mi)
+TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
+                        struct MHD_Connection *connection,
+                        struct TMH_HandlerContext *hc)
 {
-  struct PayContext *pc;
-  enum GNUNET_GenericReturnValue res;
-  MHD_RESULT ret;
-  json_t *root;
+  struct PayContext *pc = hc->ctx;
 
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "In handler for /pay.\n");
-  if (NULL == *connection_cls)
+  if (NULL == pc)
   {
     pc = GNUNET_new (struct PayContext);
     GNUNET_CONTAINER_DLL_insert (pc_head,
                                  pc_tail,
                                  pc);
-    pc->hc.cc = &pay_context_cleanup;
     pc->connection = connection;
-    *connection_cls = pc;
-    pc->mi = mi;
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "/pay: picked instance %s\n",
-                mi->id);
-  }
-  else
-  {
-    /* not the first call, recover state */
-    pc = *connection_cls;
+    pc->hc = hc;
+    hc->ctx = pc;
+    hc->cc = &pay_context_cleanup;
   }
   if (GNUNET_SYSERR == pc->suspended)
     return MHD_NO; /* during shutdown, we don't generate any more replies */
   if (0 != pc->response_code)
   {
-    /* We are *done* processing the request,
-       just queue the response (!) */
+    MHD_RESULT res;
+
+    /* We are *done* processing the request, just queue the response (!) */
     if (UINT_MAX == pc->response_code)
     {
       GNUNET_break (0);
@@ -2221,35 +1830,36 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
     MHD_destroy_response (pc->response);
     pc->response = NULL;
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Queueing response (%u) for /pay (%s).\n",
+                "Queueing response (%u) for POST /orders/$ID/pay (%s).\n",
                 (unsigned int) pc->response_code,
                 res ? "OK" : "FAILED");
     return res;
   }
-
-  res = TALER_MHD_parse_post_json (connection,
-                                   &pc->json_parse_context,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
   {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_JSON_INVALID,
-                                       "could not parse JSON");
+    enum GNUNET_GenericReturnValue ret;
+
+    ret = parse_pay (connection,
+                     hc,
+                     pc);
+    if (GNUNET_OK != ret)
+      return (GNUNET_NO == ret)
+             ? MHD_YES
+             : MHD_NO;
   }
-  if ( (GNUNET_NO == res) ||
-       (NULL == root) )
-    return MHD_YES; /* the POST's body has to be further fetched */
-
-  ret = handler_pay_json (connection,
-                          root,
-                          pc);
-  json_decref (root);
-  return ret;
+
+  /* Payment not finished, suspend while we interact with the exchange */
+  GNUNET_assert (GNUNET_NO == pc->suspended);
+  MHD_suspend_connection (connection);
+  pc->suspended = GNUNET_YES;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Suspending pay handling while working with the exchange\n");
+  GNUNET_assert (NULL == pc->timeout_task);
+  pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
+                                                   &handle_pay_timeout,
+                                                   pc);
+  begin_transaction (pc);
+  return MHD_YES;
 }
 
 
-/* end of taler-merchant-httpd_pay.c */
+/* end of taler-merchant-httpd_post-orders-ID-pay.c */
diff --git a/src/backend/taler-merchant-httpd_pay.h 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
similarity index 57%
rename from src/backend/taler-merchant-httpd_pay.h
rename to src/backend/taler-merchant-httpd_post-orders-ID-pay.h
index 726a27b..dff46de 100644
--- a/src/backend/taler-merchant-httpd_pay.h
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2017 GNUnet e.V.
+  (C) 2014-2020 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,12 +14,13 @@
   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
+ * @file backend/taler-merchant-httpd_post-orders-ID-pay.h
+ * @brief headers for POST /orders/$ID/pay handler
  * @author Marcello Stanisci
+ * @author Christian Grothoff
  */
-#ifndef TALER_EXCHANGE_HTTPD_PAY_H
-#define TALER_EXCHANGE_HTTPD_PAY_H
+#ifndef TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_PAY_H
+#define TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_PAY_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
@@ -29,26 +30,20 @@
  * to shut down MHD.
  */
 void
-MH_force_pc_resume (void);
+TMH_force_pc_resume (void);
 
 
 /**
- * Manage a payment
+ * Process payment for a claimed order.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_pay (struct TMH_RequestHandler *rh,
-                struct MHD_Connection *connection,
-                void **connection_cls,
-                const char *upload_data,
-                size_t *upload_data_size,
-                struct MerchantInstance *mi);
+TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
+                        struct MHD_Connection *connection,
+                        struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c 
b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
new file mode 100644
index 0000000..bba73d4
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
@@ -0,0 +1,932 @@
+/*
+  This file is part of TALER
+  (C) 2017-2020 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_post-tips-ID-pickup.c
+ * @brief implementation of a POST /tips/ID/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_exchanges.h"
+#include "taler-merchant-httpd_post-tips-ID-pickup.h"
+
+
+/**
+ * How often do we retry on serialization errors?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * How long do we give the exchange operation to complete withdrawing
+ * all of the planchets?
+ */
+#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_SECONDS, 45)
+
+
+/**
+ * Active pickup operations.
+ */
+struct PickupContext;
+
+
+/**
+ * Handle for an individual planchet we are processing for a tip.
+ */
+struct PlanchetOperation
+{
+  /**
+   * Active pickup operation this planchet belongs with.
+   */
+  struct PickupContext *pc;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PlanchetOperation *prev;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PlanchetOperation *next;
+
+  /**
+   * Find operation (while active), later NULL.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Withdraw handle (NULL while @e fo is active).
+   */
+  struct TALER_EXCHANGE_Withdraw2Handle *w2h;
+
+  /**
+   * Details about the planchet for withdrawing.
+   */
+  struct TALER_PlanchetDetail pd;
+
+  /**
+   * Offset of this planchet in the original request.
+   */
+  unsigned int offset;
+};
+
+
+/**
+ * Active pickup operations.
+ */
+struct PickupContext
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct PickupContext *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PickupContext *prev;
+
+  /**
+   * The connection.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Timeout task.
+   */
+  struct GNUNET_SCHEDULER_Task *tt;
+
+  /**
+   * Head of DLL of exchange operations on planchets.
+   */
+  struct PlanchetOperation *po_head;
+
+  /**
+   * Tail of DLL of exchange operations on planchets.
+   */
+  struct PlanchetOperation *po_tail;
+
+  /**
+   * HTTP response to return (set on errors).
+   */
+  struct MHD_Response *response;
+
+  /**
+   * Find operation (while active), later NULL.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Which reserve are we draining?
+   */
+  struct TALER_ReservePrivateKeyP reserve_priv;
+
+  /**
+   * Which tip is being picked up?
+   */
+  struct GNUNET_HashCode tip_id;
+
+  /**
+   * What is the ID of the pickup operation? (Basically a
+   * hash over the key inputs).
+   */
+  struct GNUNET_HashCode pickup_id;
+
+  /**
+   * Array of our planchets.
+   */
+  struct TALER_PlanchetDetail *planchets;
+
+  /**
+   * Length of the @e planchets array.
+   */
+  unsigned int planchets_length;
+
+  /**
+   * HTTP status to use (set on errors).
+   */
+  unsigned int http_status;
+
+  /**
+   * Total amount requested in the pick up operation. Computed by
+   * totaling up the amounts of all the @e planchets.
+   */
+  struct TALER_Amount total_requested;
+
+  /**
+   * True if @e total_requested has been initialized.
+   */
+  bool tr_initialized;
+};
+
+
+/**
+ * Head of DLL.
+ */
+static struct PickupContext *pc_head;
+
+/**
+ * Tail of DLL.
+ */
+static struct PickupContext *pc_tail;
+
+
+/**
+ * Stop all ongoing operations associated with @a pc.
+ */
+static void
+stop_operations (struct PickupContext *pc)
+{
+  struct PlanchetOperation *po;
+
+  if (NULL != pc->tt)
+  {
+    GNUNET_SCHEDULER_cancel (pc->tt);
+    pc->tt = NULL;
+  }
+  if (NULL != pc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
+    pc->fo = NULL;
+  }
+  while (NULL != (po = pc->po_head))
+  {
+    if (NULL != po->fo)
+    {
+      TMH_EXCHANGES_find_exchange_cancel (po->fo);
+      po->fo = NULL;
+    }
+    if (NULL != po->w2h)
+    {
+      TALER_EXCHANGE_withdraw2_cancel (po->w2h);
+      po->w2h = NULL;
+    }
+    GNUNET_CONTAINER_DLL_remove (pc->po_head,
+                                 pc->po_tail,
+                                 po);
+  }
+}
+
+
+/**
+ * Function called to clean up.
+ *
+ * @param cls a `struct PickupContext *` to clean up
+ */
+static void
+pick_context_cleanup (void *cls)
+{
+  struct PickupContext *pc = cls;
+
+  stop_operations (pc); /* should not be any... */
+  for (unsigned int i = 0; i<pc->planchets_length; i++)
+    GNUNET_free (pc->planchets[i].coin_ev);
+  GNUNET_array_grow (pc->planchets,
+                     pc->planchets_length,
+                     0);
+  GNUNET_free (pc);
+}
+
+
+/**
+ * We are shutting down, force resuming all suspended pickup operations.
+ */
+void
+TMH_force_tip_pickup_resume ()
+{
+  for (struct PickupContext *pc = pc_head;
+       NULL != pc;
+       pc = pc->next)
+  {
+    stop_operations (pc);
+    MHD_resume_connection (pc->connection);
+  }
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * withdraw request to a exchange without the (un)blinding factor.
+ * We persist the result in the database and, if we were the last
+ * planchet operation, resume HTTP processing.
+ *
+ * @param cls closure with a `struct PlanchetOperation *`
+ * @param hr HTTP response data
+ * @param blind_sig blind signature over the coin, NULL on error
+ */
+static void
+withdraw_cb (void *cls,
+             const struct TALER_EXCHANGE_HttpResponse *hr,
+             const struct GNUNET_CRYPTO_RsaSignature *blind_sig)
+{
+  struct PlanchetOperation *po = cls;
+  struct PickupContext *pc = po->pc;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_CONTAINER_DLL_remove (pc->po_head,
+                               pc->po_tail,
+                               po);
+  if (NULL == blind_sig)
+  {
+    stop_operations (pc);
+    pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+    pc->response =
+      TALER_MHD_make_json_pack (
+        "{s:I, s:I, s:I, s:O}",
+        "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_ERROR,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply);
+    MHD_resume_connection (pc->connection);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    return;
+  }
+  qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
+                                              &pc->pickup_id,
+                                              po->offset,
+                                              blind_sig);
+  if (qs < 0)
+  {
+    stop_operations (pc);
+    pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    pc->response = TALER_MHD_make_error (
+      TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+      "Could not store blind signature in DB");
+    MHD_resume_connection (pc->connection);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    return;
+  }
+  if (NULL == pc->po_head)
+  {
+    stop_operations (pc); /* stops timeout job */
+    MHD_resume_connection (pc->connection);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+  }
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation as part of a withdraw objective.  If the exchange is ready,
+ * withdraws the planchet from the exchange.
+ *
+ * @param cls closure, with our `struct PlanchetOperation *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+do_withdraw (void *cls,
+             const struct TALER_EXCHANGE_HttpResponse *hr,
+             struct TALER_EXCHANGE_Handle *eh,
+             const char *payto_uri,
+             const struct TALER_Amount *wire_fee,
+             bool exchange_trusted)
+{
+  struct PlanchetOperation *po = cls;
+  struct PickupContext *pc = po->pc;
+
+  po->fo = NULL;
+  if (NULL == eh)
+  {
+    stop_operations (pc);
+    GNUNET_CONTAINER_DLL_remove (pc->po_head,
+                                 pc->po_tail,
+                                 po);
+    pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+    pc->response =
+      TALER_MHD_make_json_pack (
+        "{s:I, s:I, s:I, s:O}",
+        "code", (json_int_t) TALER_EC_TIP_PICKUP_CONTACT_EXCHANGE_ERROR,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply);
+    MHD_resume_connection (pc->connection);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    return;
+  }
+  po->w2h = TALER_EXCHANGE_withdraw2 (eh,
+                                      &po->pd,
+                                      &pc->reserve_priv,
+                                      &withdraw_cb,
+                                      po);
+}
+
+
+/**
+ * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
+ * @a offset.  Sets up the respective operation and adds it @a pc's operation
+ * list. Once the operation is complete, the resulting blind signature is
+ * committed to the merchant's database. If all planchet operations are
+ * completed, the HTTP processing is resumed.
+ *
+ * @param[in,out] pc a pending pickup operation that includes @a planchet
+ * @param exchange_url identifies an exchange to do the pickup from
+ * @param planchet details about the coin to pick up
+ * @param offset offset of @a planchet in the list, needed to process the reply
+ */
+static void
+try_withdraw (struct PickupContext *pc,
+              const char *exchange_url,
+              const struct TALER_PlanchetDetail *planchet,
+              unsigned int offset)
+{
+  struct PlanchetOperation *po;
+
+  po = GNUNET_new (struct PlanchetOperation);
+  po->pc = pc;
+  po->pd = *planchet;
+  po->offset = offset;
+  po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+                                        NULL,
+                                        GNUNET_NO,
+                                        &do_withdraw,
+                                        po);
+  GNUNET_assert (NULL != po->fo);
+  GNUNET_CONTAINER_DLL_insert (pc->po_head,
+                               pc->po_tail,
+                               po);
+}
+
+
+/**
+ * Handle timeout for pickup.
+ *
+ * @param cls a `struct PickupContext *`
+ */
+static void
+do_timeout (void *cls)
+{
+  struct PickupContext *pc = cls;
+
+  pc->tt = NULL;
+  stop_operations (pc);
+  pc->http_status = MHD_HTTP_REQUEST_TIMEOUT;
+  pc->response =  TALER_MHD_make_error (
+    TALER_EC_TIP_PICKUP_EXCHANGE_TIMEOUT,
+    "Timeout trying to withdraw from exchange (try again later)");
+  MHD_resume_connection (pc->connection);
+  TMH_trigger_daemon ();   /* we resumed, kick MHD */
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation as part of a withdraw objective.  Here, we initialize
+ * the "total_requested" amount by adding up the cost of the planchets
+ * provided by the client.
+ *
+ * @param cls closure, with our `struct PickupContext *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+compute_total_requested (void *cls,
+                         const struct TALER_EXCHANGE_HttpResponse *hr,
+                         struct TALER_EXCHANGE_Handle *eh,
+                         const char *payto_uri,
+                         const struct TALER_Amount *wire_fee,
+                         bool exchange_trusted)
+{
+  struct PickupContext *pc = cls;
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  pc->fo = NULL;
+  stop_operations (pc); /* stops timeout job */
+  if ( (NULL == eh) ||
+       (NULL == (keys = TALER_EXCHANGE_get_keys (eh))) )
+  {
+    pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+    pc->response =
+      TALER_MHD_make_json_pack (
+        "{s:I, s:I, s:I, s:O}",
+        "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_KEYS_ERROR,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply);
+    MHD_resume_connection (pc->connection);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    return;
+  }
+  TALER_amount_get_zero (TMH_currency,
+                         &pc->total_requested);
+  for (unsigned int i = 0; i<pc->planchets_length; i++)
+  {
+    struct TALER_PlanchetDetail *pd = &pc->planchets[i];
+    const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+    dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                       &pd->denom_pub_hash);
+    if (NULL == dpk)
+    {
+      pc->http_status = MHD_HTTP_CONFLICT;
+      pc->response =
+        TALER_MHD_make_json_pack (
+          "{s:I, s:I, s:I, s:O}",
+          "code", (json_int_t) TALER_EC_TIP_PICKUP_DENOMINATION_UNKNOWN,
+          "exchange_code", (json_int_t) hr->ec,
+          "exchange_http_status", (json_int_t) hr->http_status,
+          "exchange_reply", hr->reply);
+      MHD_resume_connection (pc->connection);
+      TMH_trigger_daemon ();   /* we resumed, kick MHD */
+      return;
+    }
+
+    if ( (GNUNET_YES !=
+          TALER_amount_cmp_currency (&pc->total_requested,
+                                     &dpk->value)) ||
+         (0 >
+          TALER_amount_add (&pc->total_requested,
+                            &pc->total_requested,
+                            &dpk->value)) )
+    {
+      pc->http_status = MHD_HTTP_BAD_REQUEST;
+      pc->response =
+        TALER_MHD_make_error (TALER_EC_TIP_PICKUP_SUMMATION_FAILED,
+                              "Could not add up values to compute pickup 
total");
+      MHD_resume_connection (pc->connection);
+      TMH_trigger_daemon ();   /* we resumed, kick MHD */
+      return;
+    }
+  }
+  pc->tr_initialized = true;
+  MHD_resume_connection (pc->connection);
+  TMH_trigger_daemon ();   /* we resumed, kick MHD */
+}
+
+
+/**
+ * The tip lookup operation failed. Generate an error response based on the @a 
qs.
+ *
+ * @param connection connection to generate error for
+ * @param qs DB status to base error creation on
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_lookup_tip_failed (struct MHD_Connection *connection,
+                         enum GNUNET_DB_QueryStatus qs)
+{
+  unsigned int response_code;
+  enum TALER_ErrorCode ec;
+
+  TMH_db->rollback (TMH_db->cls);
+  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 TALER_MHD_reply_with_error (connection,
+                                     response_code,
+                                     ec,
+                                     "Could not process pickup");
+}
+
+
+/**
+ * Manages a POST /tips/$ID/pickup call, checking that the tip is authorized,
+ * and if so, returning the blind signatures.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
+                         struct MHD_Connection *connection,
+                         struct TMH_HandlerContext *hc)
+{
+  struct PickupContext *pc = hc->ctx;
+  char *exchange_url;
+  struct TALER_Amount total_authorized;
+  struct TALER_Amount total_picked_up;
+  struct TALER_Amount total_remaining;
+  struct GNUNET_TIME_Absolute expiration;
+  enum GNUNET_DB_QueryStatus qs;
+  unsigned int num_retries;
+
+  if (NULL == pc)
+  {
+    json_t *planchets;
+    json_t *planchet;
+    size_t index;
+
+    pc = GNUNET_new (struct PickupContext);
+    hc->ctx = pc;
+    hc->cc = &pick_context_cleanup;
+
+    GNUNET_assert (NULL != hc->infix);
+    if (GNUNET_OK !=
+        GNUNET_CRYPTO_hash_from_string (hc->infix,
+                                        &pc->tip_id))
+    {
+      /* tip_id has wrong encoding */
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_PARAMETER_MALFORMED,
+                                         "tip_id malformed");
+    }
+
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("planchets",
+                               &planchets),
+        GNUNET_JSON_spec_end ()
+      };
+      {
+        enum GNUNET_GenericReturnValue res;
+
+        res = TALER_MHD_parse_json_data (connection,
+                                         hc->request_body,
+                                         spec);
+        if (GNUNET_OK != res)
+          return (GNUNET_NO == res)
+                 ? MHD_YES
+                 : MHD_NO;
+      }
+    }
+    if (! json_is_array (planchets))
+    {
+      GNUNET_break_op (0);
+      json_decref (planchets);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_PATCH_INSTANCES_BAD_PAYTO_URIS,
+                                         "Invalid bank account information");
+    }
+
+    GNUNET_array_grow (pc->planchets,
+                       pc->planchets_length,
+                       json_array_size (planchets));
+    json_array_foreach (planchets, index, planchet) {
+      struct TALER_PlanchetDetail *pd = &pc->planchets[index];
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                     &pd->denom_pub_hash),
+        GNUNET_JSON_spec_varsize ("coin_ev",
+                                  (void **) &pd->coin_ev,
+                                  &pd->coin_ev_size),
+        GNUNET_JSON_spec_end ()
+      };
+      {
+        enum GNUNET_GenericReturnValue res;
+
+        res = TALER_MHD_parse_json_data (connection,
+                                         planchet,
+                                         spec);
+        if (GNUNET_OK != res)
+        {
+          json_decref (planchets);
+          return (GNUNET_NO == res)
+                 ? MHD_YES
+                 : MHD_NO;
+        }
+      }
+    }
+    json_decref (planchets);
+    {
+      struct GNUNET_HashContext *hc;
+
+      hc = GNUNET_CRYPTO_hash_context_start ();
+      GNUNET_CRYPTO_hash_context_read (hc,
+                                       &pc->tip_id,
+                                       sizeof (pc->tip_id));
+      for (unsigned int i = 0; i<pc->planchets_length; i++)
+      {
+        struct TALER_PlanchetDetail *pd = &pc->planchets[i];
+
+        GNUNET_CRYPTO_hash_context_read (hc,
+                                         &pd->denom_pub_hash,
+                                         sizeof (pd->denom_pub_hash));
+        GNUNET_CRYPTO_hash_context_read (hc,
+                                         pd->coin_ev,
+                                         pd->coin_ev_size);
+      }
+      GNUNET_CRYPTO_hash_context_finish (hc,
+                                         &pc->pickup_id);
+    }
+  }
+
+  if (NULL != pc->response)
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (connection,
+                              pc->http_status,
+                              pc->response);
+    pc->response = NULL;
+    return ret;
+  }
+
+  if (! pc->tr_initialized)
+  {
+    qs = TMH_db->lookup_tip (TMH_db->cls,
+                             hc->instance->settings.id,
+                             &pc->tip_id,
+                             &total_authorized,
+                             &total_picked_up,
+                             &expiration,
+                             &exchange_url,
+                             &pc->reserve_priv);
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+      return reply_lookup_tip_failed (connection,
+                                      qs);
+    MHD_suspend_connection (connection);
+    pc->connection = connection;
+    pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+                                           &do_timeout,
+                                           pc);
+    pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+                                          NULL,
+                                          GNUNET_NO,
+                                          &compute_total_requested,
+                                          pc);
+    return MHD_YES;
+  }
+
+
+  TMH_db->preflight (TMH_db->cls);
+  num_retries = 0;
+RETRY:
+  num_retries++;
+  if (num_retries > MAX_RETRIES)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_ERROR_SOFT,
+                                       "Too many DB serialization failures");
+  }
+  if (GNUNET_OK !=
+      TMH_db->start (TMH_db->cls,
+                     "pickup tip"))
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_ERROR_HARD,
+                                       "Could not begin transaction");
+  }
+  {
+    struct GNUNET_CRYPTO_RsaSignature *sigs[GNUNET_NZL (pc->planchets_length)];
+    memset (sigs,
+            0,
+            sizeof (struct GNUNET_CRYPTO_RsaSignature *) * GNUNET_NZL (
+              pc->planchets_length));
+
+    qs = TMH_db->lookup_pickup (TMH_db->cls,
+                                hc->instance->settings.id,
+                                &pc->tip_id,
+                                &pc->pickup_id,
+                                &exchange_url,
+                                &pc->reserve_priv,
+                                pc->planchets_length,
+                                sigs);
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    {
+      for (unsigned int i = 0; i< pc->planchets_length; i++)
+      {
+        if (NULL == sigs[i])
+        {
+          try_withdraw (pc,
+                        exchange_url,
+                        &pc->planchets[i],
+                        i);
+          qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+        }
+      }
+      if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      {
+        MHD_suspend_connection (connection);
+        GNUNET_CONTAINER_DLL_insert (pc_head,
+                                     pc_tail,
+                                     pc);
+        pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+                                               &do_timeout,
+                                               pc);
+        TMH_db->rollback (TMH_db->cls);
+        return MHD_YES;
+      }
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+    {
+      unsigned int response_code;
+      enum TALER_ErrorCode ec;
+
+      TMH_db->rollback (TMH_db->cls);
+      switch (qs)
+      {
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+        {
+          json_t *blind_sigs;
+
+          blind_sigs = json_array ();
+          GNUNET_assert (NULL != blind_sigs);
+          for (unsigned int i = 0; i<pc->planchets_length; i++)
+          {
+            GNUNET_assert (0 ==
+                           json_array_append_new (
+                             blind_sigs,
+                             json_pack ("{s:o}",
+                                        "blind_sig",
+                                        GNUNET_JSON_from_rsa_signature (
+                                          sigs[i]))));
+            GNUNET_CRYPTO_rsa_signature_free (sigs[i]);
+          }
+          return TALER_MHD_reply_json_pack (
+            connection,
+            MHD_HTTP_OK,
+            "{s:o}",
+            "blind_sigs", blind_sigs);
+        }
+        break;
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        goto RETRY;
+        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 TALER_MHD_reply_with_error (connection,
+                                         response_code,
+                                         ec,
+                                         "Could not process pickup");
+    }
+  }
+
+  qs = TMH_db->lookup_tip (TMH_db->cls,
+                           hc->instance->settings.id,
+                           &pc->tip_id,
+                           &total_authorized,
+                           &total_picked_up,
+                           &expiration,
+                           &exchange_url,
+                           &pc->reserve_priv);
+  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    goto RETRY;
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+    return reply_lookup_tip_failed (connection,
+                                    qs);
+  if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_GONE,
+                                       TALER_EC_TIP_PICKUP_HAS_EXPIRED,
+                                       "Could not process pickup: it is too 
late");
+  }
+  if (0 >
+      TALER_amount_subtract (&total_remaining,
+                             &total_authorized,
+                             &total_picked_up))
+  {
+    GNUNET_break (0);
+    TMH_db->rollback (TMH_db->cls);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_LOGIC_ERROR,
+                                       "tip already overdrawn");
+  }
+
+  if (0 >
+      TALER_amount_cmp (&total_remaining,
+                        &pc->total_requested))
+  {
+    GNUNET_break (0);
+    TMH_db->rollback (TMH_db->cls);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
+                                       "requested amount exceeds amount left 
in tip");
+  }
+
+  GNUNET_assert (0 <
+                 TALER_amount_add (&total_picked_up,
+                                   &total_picked_up,
+                                   &pc->total_requested));
+  qs = TMH_db->insert_pickup (TMH_db->cls,
+                              hc->instance->settings.id,
+                              &pc->tip_id,
+                              &total_picked_up,
+                              &pc->pickup_id,
+                              &pc->total_requested);
+  if (qs < 0)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto RETRY;
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+                                       "Could not store pickup ID in 
database");
+  }
+  qs = TMH_db->commit (TMH_db->cls);
+  if (qs < 0)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto RETRY;
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+                                       "Could not commit transaction to 
database");
+  }
+  MHD_suspend_connection (connection);
+  pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+                                         &do_timeout,
+                                         pc);
+  for (unsigned int i = 0; i<pc->planchets_length; i++)
+  {
+    try_withdraw (pc,
+                  exchange_url,
+                  &pc->planchets[i],
+                  i);
+  }
+  return MHD_YES;
+}
diff --git a/src/backend/taler-merchant-httpd_poll-payment.h 
b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.h
similarity index 55%
copy from src/backend/taler-merchant-httpd_poll-payment.h
copy to src/backend/taler-merchant-httpd_post-tips-ID-pickup.h
index ac13c4a..f8ac486 100644
--- a/src/backend/taler-merchant-httpd_poll-payment.h
+++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.h
@@ -14,34 +14,36 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_poll-payment.h
- * @brief headers for /public/poll-payment handler
+ * @file backend/taler-merchant-httpd_post-tips-ID-pickup.h
+ * @brief headers for POST /tips/ID/pickup handler
  * @author Christian Grothoff
- * @author Florian Dold
  */
-#ifndef TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
-#define TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
+#ifndef TALER_MERCHANT_HTTPD_POST_TIPS_ID_PICKUP_H
+#define TALER_MERCHANT_HTTPD_POST_TIPS_ID_PICKUP_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
+
+/**
+ * We are shutting down, force resuming all suspended pickup operations.
+ */
+void
+TMH_force_tip_pickup_resume (void);
+
+
 /**
- * Manages a /public/poll-payment call, checking the status
- * of a payment.
+ * Manages a POST /tips/$ID/pickup call, checking that the tip is authorized,
+ * and if so, returning the blind signatures.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_poll_payment (struct TMH_RequestHandler *rh,
+TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
                          struct MHD_Connection *connection,
-                         void **connection_cls,
-                         const char *upload_data,
-                         size_t *upload_data_size,
-                         struct MerchantInstance *mi);
+                         struct TMH_HandlerContext *hc);
+
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-instances-ID.c 
b/src/backend/taler-merchant-httpd_private-delete-instances-ID.c
new file mode 100644
index 0000000..965de32
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-instances-ID.c
@@ -0,0 +1,90 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-delete-instances-ID.c
+ * @brief implement DELETE /instances/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-instances-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/instances/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_instances_ID (const struct TMH_RequestHandler *rh,
+                                 struct MHD_Connection *connection,
+                                 struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  const char *purge;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (NULL != mi);
+  purge = MHD_lookup_connection_value (connection,
+                                       MHD_GET_ARGUMENT_KIND,
+                                       "purge");
+  if ( (NULL != purge) &&
+       (0 == strcmp (purge,
+                     "yes")) )
+    qs = TMH_db->purge_instance (TMH_db->cls,
+                                 mi->settings.id);
+  else
+    qs = TMH_db->delete_instance_private_key (TMH_db->cls,
+                                              mi->settings.id);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_DELETE_INSTANCES_ID_DB_HARD_FAILURE,
+                                       "Transaction failed");
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                       "Serialization error for single SQL 
statement");
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       
TALER_EC_DELETE_INSTANCES_ID_NO_SUCH_INSTANCE,
+                                       ( (NULL != purge) &&
+                                         (0 == strcmp (purge,
+                                                       "yes")) )
+                                       ? "Instance unknown"
+                                       : "Private key unknown");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    TMH_instance_decref (mi);
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  GNUNET_assert (0);
+  return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-instances-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-instances-ID.h 
b/src/backend/taler-merchant-httpd_private-delete-instances-ID.h
new file mode 100644
index 0000000..0b28dbb
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-instances-ID.h
@@ -0,0 +1,41 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-delete-instances-ID.h
+ * @brief implement DELETE /instances/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_INSTANCES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_INSTANCES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/instances/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_instances_ID (const struct TMH_RequestHandler *rh,
+                                 struct MHD_Connection *connection,
+                                 struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-instances-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c 
b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
new file mode 100644
index 0000000..e01e752
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
@@ -0,0 +1,100 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-delete-orders-ID.c
+ * @brief implement DELETE /orders/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-orders-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/orders/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
+                              struct MHD_Connection *connection,
+                              struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (NULL != mi);
+  qs = TMH_db->delete_order (TMH_db->cls,
+                             mi->settings.id,
+                             hc->infix);
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    qs = TMH_db->delete_contract_terms (TMH_db->cls,
+                                        mi->settings.id,
+                                        hc->infix,
+                                        TMH_legal_expiration);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_ORDERS_DELETE_DB_HARD_FAILURE,
+                                       "Transaction failed");
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                       "Serialization error for single SQL 
statement");
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    qs = TMH_db->lookup_order (TMH_db->cls,
+                               mi->settings.id,
+                               hc->infix,
+                               NULL);
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      uint64_t order_serial;
+
+      qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                          mi->settings.id,
+                                          hc->infix,
+                                          NULL,
+                                          &order_serial);
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_ORDERS_DELETE_NO_SUCH_ORDER,
+                                         "Order unknown");
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       TALER_EC_ORDERS_DELETE_AWAITING_PAYMENT,
+                                       "Order deletion impossible, order is 
locked");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  GNUNET_assert (0);
+  return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-orders-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-orders-ID.h 
b/src/backend/taler-merchant-httpd_private-delete-orders-ID.h
new file mode 100644
index 0000000..e67f5e4
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-orders-ID.h
@@ -0,0 +1,41 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-delete-orders-ID.h
+ * @brief implement DELETE /orders/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ORDERS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_ORDERS_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/orders/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_orders_ID (const struct TMH_RequestHandler *rh,
+                              struct MHD_Connection *connection,
+                              struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-orders-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-products-ID.c 
b/src/backend/taler-merchant-httpd_private-delete-products-ID.c
new file mode 100644
index 0000000..fe04d76
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-products-ID.c
@@ -0,0 +1,85 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-delete-products-ID.c
+ * @brief implement DELETE /products/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-products-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/products/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_products_ID (const struct TMH_RequestHandler *rh,
+                                struct MHD_Connection *connection,
+                                struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (NULL != mi);
+  qs = TMH_db->delete_product (TMH_db->cls,
+                               mi->settings.id,
+                               hc->infix);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_PRODUCTS_DELETE_DB_HARD_FAILURE,
+                                       "Transaction failed");
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                       "Serialization error for single SQL 
statement");
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    qs = TMH_db->lookup_product (TMH_db->cls,
+                                 mi->settings.id,
+                                 hc->infix,
+                                 NULL);
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_PRODUCTS_DELETE_NO_SUCH_PRODUCT,
+                                         "Product unknown");
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       
TALER_EC_PRODUCTS_DELETE_CONFLICTING_LOCK,
+                                       "Product deletion impossible, product 
is locked");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  GNUNET_assert (0);
+  return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-products-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-products-ID.h 
b/src/backend/taler-merchant-httpd_private-delete-products-ID.h
new file mode 100644
index 0000000..507eedf
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-products-ID.h
@@ -0,0 +1,41 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-delete-products-ID.h
+ * @brief implement DELETE /products/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_PRODUCTS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_PRODUCTS_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/products/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_products_ID (const struct TMH_RequestHandler *rh,
+                                struct MHD_Connection *connection,
+                                struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-products-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.c 
b/src/backend/taler-merchant-httpd_private-delete-reserves-ID.c
new file mode 100644
index 0000000..ba2c08b
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-reserves-ID.c
@@ -0,0 +1,100 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-delete-reserves-ID.c
+ * @brief implement DELETE /reserves/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-reserves-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/reserves/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_reserves_ID (const struct TMH_RequestHandler *rh,
+                                struct MHD_Connection *connection,
+                                struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  enum GNUNET_DB_QueryStatus qs;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  const char *purge;
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (hc->infix,
+                                     strlen (hc->infix),
+                                     &reserve_pub,
+                                     sizeof (reserve_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_RESERVES_INVALID_RESERVE_PUB,
+                                       "reserve public key malformed");
+  }
+  purge = MHD_lookup_connection_value (connection,
+                                       MHD_GET_ARGUMENT_KIND,
+                                       "purge");
+  GNUNET_assert (NULL != mi);
+  if ( (NULL != purge) &&
+       (0 == strcmp (purge,
+                     "yes")) )
+    qs = TMH_db->purge_reserve (TMH_db->cls,
+                                mi->settings.id,
+                                &reserve_pub);
+  else
+    qs = TMH_db->delete_reserve (TMH_db->cls,
+                                 mi->settings.id,
+                                 &reserve_pub);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_RESERVES_DELETE_DB_HARD_FAILURE,
+                                       "Transaction failed");
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                       "Serialization error for single SQL 
statement");
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       
TALER_EC_RESERVES_DELETE_NO_SUCH_RESERVE,
+                                       "Reserve unknown");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  GNUNET_assert (0);
+  return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-reserves-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-reserves-ID.h 
b/src/backend/taler-merchant-httpd_private-delete-reserves-ID.h
new file mode 100644
index 0000000..b961d0d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-reserves-ID.h
@@ -0,0 +1,41 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-delete-reserves-ID.h
+ * @brief implement DELETE /reserves/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_RESERVES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_RESERVES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/reserves/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_delete_reserves_ID (const struct TMH_RequestHandler *rh,
+                                struct MHD_Connection *connection,
+                                struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-reserves-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID.c 
b/src/backend/taler-merchant-httpd_private-get-instances-ID.c
new file mode 100644
index 0000000..bf248b1
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-instances-ID.c
@@ -0,0 +1,102 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-instances-ID.c
+ * @brief implement GET /instances/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-instances-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/instances/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_instances_ID (const struct TMH_RequestHandler *rh,
+                              struct MHD_Connection *connection,
+                              struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  json_t *ja;
+
+  GNUNET_assert (NULL != mi);
+  ja = json_array ();
+  GNUNET_assert (NULL != ja);
+  for (struct TMH_WireMethod *wm = mi->wm_head;
+       NULL != wm;
+       wm = wm->next)
+  {
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (
+        ja,
+        json_pack (
+          "{s:O, s:o, s:O, s:b}",
+          "payto_uri",
+          json_object_get (wm->j_wire,
+                           "payto_uri"),
+          "h_wire",
+          GNUNET_JSON_from_data_auto (&wm->h_wire),
+          "salt",
+          json_object_get (wm->j_wire,
+                           "salt"),
+          "active",
+          (wm->active) ? 1 : 0)));
+  }
+
+  return TALER_MHD_reply_json_pack (
+    connection,
+    MHD_HTTP_OK,
+    "{s:o, s:s, s:o, s:O, s:O,"
+    " s:o, s:o, s:I, s:o, s:o}",
+    "accounts",
+    ja,
+    "name",
+    mi->settings.name,
+    "merchant_pub",
+    GNUNET_JSON_from_data_auto (
+      &mi->merchant_pub),
+    "address",
+    mi->settings.address,
+    "jurisdiction",
+    mi->settings.jurisdiction,
+    /* end of first group of 5 */
+    "default_max_wire_fee",
+    TALER_JSON_from_amount (
+      &mi->settings.default_max_wire_fee),
+    "default_max_deposit_fee",
+    TALER_JSON_from_amount (
+      &mi->settings.default_max_wire_fee),
+    "default_wire_fee_amortization",
+    (json_int_t)
+    mi->settings.default_wire_fee_amortization,
+    "default_wire_transfer_delay",
+    GNUNET_JSON_from_time_rel (
+      mi->settings.default_wire_transfer_delay),
+    "default_pay_delay",
+    GNUNET_JSON_from_time_rel (
+      mi->settings.default_pay_delay));
+}
+
+
+/* end of taler-merchant-httpd_private-get-instances-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID.h 
b/src/backend/taler-merchant-httpd_private-get-instances-ID.h
new file mode 100644
index 0000000..8532ad1
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-instances-ID.h
@@ -0,0 +1,41 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-instances-ID.h
+ * @brief implement GET /instances/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/instances/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_instances_ID (const struct TMH_RequestHandler *rh,
+                              struct MHD_Connection *connection,
+                              struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-instances-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-instances.c 
b/src/backend/taler-merchant-httpd_private-get-instances.c
new file mode 100644
index 0000000..e42cea2
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-instances.c
@@ -0,0 +1,116 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-instances.c
+ * @brief implement GET /instances
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-instances.h"
+
+/**
+ * Add merchant instance to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param key unused
+ * @param value a `struct TMH_MerchantInstance *`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static int
+add_instance (void *cls,
+              const struct GNUNET_HashCode *key,
+              void *value)
+{
+  json_t *ja = cls;
+  struct TMH_MerchantInstance *mi = value;
+  json_t *pta;
+
+  (void) key;
+  /* Compile array of all unique wire methods supported by this
+     instance */
+  pta = json_array ();
+  GNUNET_assert (NULL != pta);
+  for (struct TMH_WireMethod *wm = mi->wm_head;
+       NULL != wm;
+       wm = wm->next)
+  {
+    bool duplicate = false;
+
+    if (! wm->active)
+      break;
+    /* Yes, O(n^2), but really how many bank accounts can an
+       instance realistically have for this to matter? */
+    for (struct TMH_WireMethod *pm = mi->wm_head;
+         pm != wm;
+         pm = pm->next)
+      if (0 == strcasecmp (pm->wire_method,
+                           wm->wire_method))
+      {
+        duplicate = true;
+        break;
+      }
+    if (duplicate)
+      continue;
+    GNUNET_assert (0 ==
+                   json_array_append_new (pta,
+                                          json_string (wm->wire_method)));
+  }
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   ja,
+                   json_pack (
+                     "{s:s, s:s, s:o, s:o}",
+                     "name",
+                     mi->settings.name,
+                     "id",
+                     mi->settings.id,
+                     "merchant_pub",
+                     GNUNET_JSON_from_data_auto (&mi->merchant_pub),
+                     "payment_targets",
+                     pta)));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Handle a GET "/instances" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_instances (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc)
+{
+  json_t *ia;
+
+  (void) hc;
+  ia = json_array ();
+  GNUNET_assert (NULL != ia);
+  GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
+                                         &add_instance,
+                                         ia);
+  return TALER_MHD_reply_json_pack (connection,
+                                    MHD_HTTP_OK,
+                                    "{s:o}",
+                                    "instances", ia);
+}
+
+
+/* end of taler-merchant-httpd_private-get-instances.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-instances.h 
b/src/backend/taler-merchant-httpd_private-get-instances.h
new file mode 100644
index 0000000..e9e1e79
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-instances.h
@@ -0,0 +1,41 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-instances.h
+ * @brief implement GET /instances
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_INSTANCES_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/instances" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_instances (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-instances.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c 
b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
new file mode 100644
index 0000000..e983273
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -0,0 +1,1156 @@
+/*
+  This file is part of TALER
+  (C) 2017, 2019, 2020 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_private-get-orders-ID.c
+ * @brief implementation of GET /private/orders/ID handler
+ * @author Florian Dold
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-orders-ID.h"
+#include "taler-merchant-httpd_get-orders-ID.h"
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_private-get-orders.h"
+
+
+/**
+ * How long do we wait on the exchange?
+ */
+#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_SECONDS, 30)
+
+
+/**
+ * Data structure we keep for a check payment request.
+ */
+struct GetOrderRequestContext;
+
+
+/**
+ * Request to an exchange for details about wire transfers
+ * in response to a coin's deposit operation.
+ */
+struct TransferQuery
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct TransferQuery *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct TransferQuery *prev;
+
+  /**
+   * Handle to query exchange about deposit status.
+   */
+  struct TALER_EXCHANGE_DepositGetHandle *dgh;
+
+  /**
+   * Handle for ongoing exchange operation.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Overall request this TQ belongs with.
+   */
+  struct GetOrderRequestContext *gorc;
+
+  /**
+   * Hash of the merchant's bank account the transfer (presumably) went to.
+   */
+  struct GNUNET_HashCode h_wire;
+
+  /**
+   * Value deposited (including deposit fee).
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Deposit fee paid for this coin.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Public key of the coin this is about.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Which deposit operation is this about?
+   */
+  uint64_t deposit_serial;
+
+};
+
+
+/**
+ * Data structure we keep for a check payment request.
+ */
+struct GetOrderRequestContext
+{
+
+  /**
+   * Entry in the #resume_timeout_heap for this check payment, if we are
+   * suspended.
+   */
+  struct TMH_SuspendedConnection sc;
+
+  /**
+   * Which merchant instance is this for?
+   */
+  struct TMH_HandlerContext *hc;
+
+  /**
+   * session of the client
+   */
+  const char *session_id;
+
+  /**
+   * Fulfillment URL extracted from the contract. For repurchase detection.
+   * Only valid as long as @e contract_terms is valid!
+   */
+  const char *fulfillment_url;
+
+  /**
+   * Kept in a DLL while suspended on exchange.
+   */
+  struct GetOrderRequestContext *next;
+
+  /**
+   * Kept in a DLL while suspended on exchange.
+   */
+  struct GetOrderRequestContext *prev;
+
+  /**
+   * Handle to the exchange, only valid while the @e fo succeeds.
+   */
+  struct TALER_EXCHANGE_Handle *eh;
+
+  /**
+   * Head of DLL of individual queries for transfer data.
+   */
+  struct TransferQuery *tq_head;
+
+  /**
+   * Tail of DLL of individual queries for transfer data.
+   */
+  struct TransferQuery *tq_tail;
+
+  /**
+   * Timeout task while waiting on exchange.
+   */
+  struct GNUNET_SCHEDULER_Task *tt;
+
+  /**
+   * Contract terms of the payment we are checking. NULL when they
+   * are not (yet) known.
+   */
+  json_t *contract_terms;
+
+  /**
+   * Wire details for the payment, to be returned in the reply. NULL
+   * if not available.
+   */
+  json_t *wire_details;
+
+  /**
+   * Problems we encountered when looking up Wire details
+   * for the payment, to be returned.  NULL if not available.
+   */
+  json_t *wire_reports;
+
+  /**
+   * Details about refunds, NULL if there are no refunds.
+   */
+  json_t *refund_details;
+
+  /**
+   * Hash over the @e contract_terms.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * Total amount the exchange deposited into our bank account
+   * (confirmed or unconfirmed), excluding fees.
+   */
+  struct TALER_Amount deposits_total;
+
+  /**
+   * Total amount in deposit fees we paid for all coins.
+   */
+  struct TALER_Amount deposit_fees_total;
+
+  /**
+   * Total value of the coins that the exchange deposited into our bank
+   * account (confirmed or unconfirmed), including deposit fees.
+   */
+  struct TALER_Amount value_total;
+
+  /**
+   * Total we were to be paid under the contract, excluding refunds.
+   */
+  struct TALER_Amount contract_amount;
+
+  /**
+   * Serial ID of the order.
+   */
+  uint64_t order_serial;
+
+  /**
+   * Total refunds granted for this payment. Only initialized
+   * if @e refunded is set to true.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Exchange HTTP error code encountered while trying to determine wire 
transfer
+   * details. #TALER_EC_NONE for no error encountered.
+   */
+  unsigned int exchange_hc;
+
+  /**
+   * Exchange error code encountered while trying to determine wire transfer
+   * details. #TALER_EC_NONE for no error encountered.
+   */
+  enum TALER_ErrorCode exchange_ec;
+
+  /**
+   * Error code encountered while trying to determine wire transfer
+   * details. #TALER_EC_NONE for no error encountered.
+   */
+  enum TALER_ErrorCode wire_ec;
+
+  /**
+   * HTTP status to return with @e wire_ec, 0 if @e wire_ec is #TALER_EC_NONE.
+   */
+  unsigned int wire_hc;
+
+  /**
+   * Set to true if this payment has been refunded and
+   * @e refund_amount is initialized.
+   */
+  bool refunded;
+
+  /**
+   * Did the client request us to fetch the wire transfer status?
+   * If false, we may still return it if it is available.
+   */
+  bool transfer_status_requested;
+
+};
+
+
+/**
+ * Head of list of suspended requests waiting on the exchange.
+ */
+static struct GetOrderRequestContext *gorc_head;
+
+/**
+ * Tail of list of suspended requests waiting on the exchange.
+ */
+static struct GetOrderRequestContext *gorc_tail;
+
+
+/**
+ * Resume processing the request, cancelling all pending asynchronous
+ * operations.
+ *
+ * @param gorc request to resume
+ * @param http_status HTTP status to return, 0 to continue with success
+ * @param ec error code for the request, #TALER_EC_NONE on success
+ */
+static void
+gorc_resume (struct GetOrderRequestContext *gorc,
+             unsigned int http_status,
+             enum TALER_ErrorCode ec)
+{
+  struct TransferQuery *tq;
+
+  if (NULL != gorc->tt)
+  {
+    GNUNET_SCHEDULER_cancel (gorc->tt);
+    gorc->tt = NULL;
+  }
+  while (NULL != (tq = gorc->tq_head))
+  {
+    if (NULL != tq->fo)
+    {
+      TMH_EXCHANGES_find_exchange_cancel (tq->fo);
+      tq->fo = NULL;
+    }
+    if (NULL != tq->dgh)
+    {
+      TALER_EXCHANGE_deposits_get_cancel (tq->dgh);
+      tq->dgh = NULL;
+    }
+  }
+  gorc->wire_hc = http_status;
+  gorc->wire_ec = ec;
+  GNUNET_CONTAINER_DLL_remove (gorc_head,
+                               gorc_tail,
+                               gorc);
+  MHD_resume_connection (gorc->sc.con);
+  TMH_trigger_daemon ();   /* we resumed, kick MHD */
+}
+
+
+/**
+ * Add a report about trouble obtaining wire transfer data to the reply.
+ *
+ * @param gorc request to add wire report to
+ * @param ec error code to add
+ * @param hint human-readable hint
+ * @param coin_pub public key of the affected coin
+ * @param exchange_hr details from exchange, NULL if exchange is blameless
+ */
+static void
+gorc_report (struct GetOrderRequestContext *gorc,
+             enum TALER_ErrorCode ec,
+             const char *hint,
+             struct TALER_CoinSpendPublicKeyP *coin_pub,
+             const struct TALER_EXCHANGE_HttpResponse *exchange_hr)
+{
+  if (NULL != exchange_hr)
+    GNUNET_assert (0 ==
+                   json_array_append_new (
+                     gorc->wire_reports,
+                     json_pack ("{s:I, s:s, s:o, s:I, s:I}",
+                                "code",
+                                (json_int_t) ec,
+                                "hint",
+                                hint,
+                                "coin_pub",
+                                GNUNET_JSON_from_data_auto (coin_pub),
+                                "exchange_ec",
+                                (json_int_t) exchange_hr->ec,
+                                "exchange_hc",
+                                (json_int_t) exchange_hr->http_status)));
+  else
+    GNUNET_assert (0 ==
+                   json_array_append_new (
+                     gorc->wire_reports,
+                     json_pack ("{s:I, s:s, s:o}",
+                                "code",
+                                (json_int_t) ec,
+                                "hint",
+                                hint,
+                                "coin_pub",
+                                GNUNET_JSON_from_data_auto (coin_pub))));
+}
+
+
+/**
+ * Timeout trying to get current wire transfer data from the exchange.
+ * Clean up and continue.
+ *
+ * @param cls closure, must be a `struct GetOrderRequestContext *`
+ */
+static void
+exchange_timeout_cb (void *cls)
+{
+  struct GetOrderRequestContext *gorc = cls;
+
+  gorc->tt = NULL;
+  gorc_resume (gorc,
+               MHD_HTTP_REQUEST_TIMEOUT,
+               TALER_EC_GET_ORDERS_EXCHANGE_TIMEOUT);
+}
+
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure with a `struct TransferQuery *`
+ * @param hr HTTP response data
+ * @param dd details about the deposit (NULL on errors)
+ */
+static void
+deposit_get_cb (void *cls,
+                const struct TALER_EXCHANGE_HttpResponse *hr,
+                const struct TALER_EXCHANGE_DepositData *dd)
+{
+  struct TransferQuery *tq = cls;
+  struct GetOrderRequestContext *gorc = tq->gorc;
+
+  GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+                               gorc->tq_tail,
+                               tq);
+  if (NULL == dd)
+  {
+    gorc_report (gorc,
+                 TALER_EC_GET_ORDERS_EXCHANGE_TRACKING_FAILURE,
+                 "Exchange failed to return tracking data",
+                 &tq->coin_pub,
+                 hr);
+    GNUNET_free (tq);
+    if (NULL == gorc->tq_head)
+      gorc_resume (gorc,
+                   0,
+                   TALER_EC_NONE);
+    return;
+  }
+  else if (MHD_HTTP_OK == hr->http_status)
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls,
+                                             tq->deposit_serial,
+                                             dd);
+    if (qs < 0)
+    {
+      gorc_report (gorc,
+                   TALER_EC_GET_ORDERS_DB_STORE_TRACKING_FAILURE,
+                   "Merchant backend failed to store transfer details in DB",
+                   &tq->coin_pub,
+                   NULL);
+      GNUNET_free (tq);
+      if (NULL == gorc->tq_head)
+        gorc_resume (gorc,
+                     0,
+                     TALER_EC_NONE);
+      return;
+    }
+    /* Compute total amount *wired* */
+    if (0 >
+        TALER_amount_add (&gorc->deposits_total,
+                          &gorc->deposits_total,
+                          &dd->coin_contribution))
+    {
+      gorc_report (gorc,
+                   TALER_EC_GET_ORDERS_AMOUNT_ARITHMETIC_FAILURE,
+                   "Merchant backend could not sum up deposit total",
+                   &tq->coin_pub,
+                   NULL);
+      GNUNET_free (tq);
+      if (NULL == gorc->tq_head)
+        gorc_resume (gorc,
+                     0,
+                     TALER_EC_NONE);
+      return;
+    }
+    if (0 >
+        TALER_amount_add (&gorc->deposit_fees_total,
+                          &gorc->deposit_fees_total,
+                          &tq->deposit_fee))
+    {
+      gorc_report (gorc,
+                   TALER_EC_GET_ORDERS_AMOUNT_ARITHMETIC_FAILURE,
+                   "Merchant backend could not sum up deposit fees",
+                   &tq->coin_pub,
+                   NULL);
+      GNUNET_free (tq);
+      if (NULL == gorc->tq_head)
+        gorc_resume (gorc,
+                     0,
+                     TALER_EC_NONE);
+      return;
+    }
+  }
+  else
+  {
+    /* got a 'preliminary' reply from the exchange, simply skip */
+    gorc_report (gorc,
+                 TALER_EC_NONE,
+                 "Exchange returned preliminary response",
+                 &tq->coin_pub,
+                 hr);
+  }
+  GNUNET_free (tq);
+  if (NULL != gorc->tq_head)
+    return;
+  /* *all* are done, resume! */
+  gorc_resume (gorc,
+               0,
+               TALER_EC_NONE);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @param cls closure with a `struct GetOrderRequestContext *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+exchange_found_cb (void *cls,
+                   const struct TALER_EXCHANGE_HttpResponse *hr,
+                   struct TALER_EXCHANGE_Handle *eh,
+                   const char *payto_uri,
+                   const struct TALER_Amount *wire_fee,
+                   bool exchange_trusted)
+{
+  struct TransferQuery *tq = cls;
+  struct GetOrderRequestContext *gorc = tq->gorc;
+
+  tq->fo = NULL;
+  if (NULL == eh)
+  {
+    /* failed */
+    GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+                                 gorc->tq_tail,
+                                 tq);
+    GNUNET_free (tq);
+    gorc->exchange_hc = hr->http_status;
+    gorc->exchange_ec = hr->ec;
+    gorc_resume (gorc,
+                 MHD_HTTP_FAILED_DEPENDENCY,
+                 TALER_EC_GET_ORDERS_EXCHANGE_LOOKUP_FAILURE);
+    return;
+  }
+  tq->dgh = TALER_EXCHANGE_deposits_get (eh,
+                                         &gorc->hc->instance->merchant_priv,
+                                         &tq->h_wire,
+                                         &gorc->h_contract_terms,
+                                         &tq->coin_pub,
+                                         &deposit_get_cb,
+                                         tq);
+  if (NULL == tq->dgh)
+  {
+    GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+                                 gorc->tq_tail,
+                                 tq);
+    GNUNET_free (tq);
+    gorc_resume (gorc,
+                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                 TALER_EC_GET_ORDERS_EXCHANGE_REQUEST_FAILURE);
+  }
+}
+
+
+/**
+ * Function called with each @a coin_pub that was deposited into the
+ * @a h_wire account of the merchant for the @a deposit_serial as part
+ * of the payment for the order identified by @a cls.
+ *
+ * Queries the exchange for the payment status associated with the
+ * given coin.
+ *
+ * @param cls a `struct GetOrderRequestContext`
+ * @param deposit_serial identifies the deposit operation
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param h_wire hash of the merchant's wire account into which the deposit 
was made
+ * @param coin_pub public key of the deposited coin
+ */
+static void
+deposit_cb (void *cls,
+            uint64_t deposit_serial,
+            const char *exchange_url,
+            const struct GNUNET_HashCode *h_wire,
+            const struct TALER_Amount *amount_with_fee,
+            const struct TALER_Amount *deposit_fee,
+            const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+  struct GetOrderRequestContext *gorc = cls;
+  struct TransferQuery *tq;
+
+  tq = GNUNET_new (struct TransferQuery);
+  tq->gorc = gorc;
+  tq->deposit_serial = deposit_serial;
+  GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
+                               gorc->tq_tail,
+                               tq);
+  tq->coin_pub = *coin_pub;
+  tq->h_wire = *h_wire;
+  tq->amount_with_fee = *amount_with_fee;
+  tq->deposit_fee = *deposit_fee;
+  tq->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+                                        NULL,
+                                        GNUNET_NO,
+                                        &exchange_found_cb,
+                                        tq);
+  if (NULL == tq->fo)
+  {
+    gorc_resume (gorc,
+                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                 TALER_EC_GET_ORDERS_EXCHANGE_LOOKUP_START_FAILURE);
+  }
+}
+
+
+/**
+ * Clean up the session state for a GET /private/order/ID request.
+ *
+ * @param cls closure, must be a `struct GetOrderRequestContext *`
+ */
+static void
+gorc_cleanup (void *cls)
+{
+  struct GetOrderRequestContext *gorc = cls;
+
+  if (NULL != gorc->contract_terms)
+    json_decref (gorc->contract_terms);
+  if (NULL != gorc->wire_details)
+    json_decref (gorc->wire_details);
+  if (NULL != gorc->refund_details)
+    json_decref (gorc->refund_details);
+  if (NULL != gorc->wire_reports)
+    json_decref (gorc->wire_reports);
+  GNUNET_assert (NULL == gorc->tt);
+  GNUNET_free (gorc);
+}
+
+
+/**
+ * Function called with information about a refund.
+ * It is responsible for summing up the refund amount.
+ *
+ * @param cls closure
+ * @param refund_serial unique serial number of the refund
+ * @param timestamp time of the refund (for grouping of refunds in the wallet 
UI)
+ * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explanation of the refund
+ * @param timestamp when was the refund made
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ */
+static void
+process_refunds_cb (void *cls,
+                    uint64_t refund_serial,
+                    struct GNUNET_TIME_Absolute timestamp,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
+                    uint64_t rtransaction_id,
+                    const char *reason,
+                    const struct TALER_Amount *refund_amount)
+{
+  struct GetOrderRequestContext *gorc = cls;
+
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   gorc->refund_details,
+                   json_pack ("{s:o, s:o, s:s}",
+                              "amount",
+                              TALER_JSON_from_amount (refund_amount),
+                              "timestamp",
+                              GNUNET_JSON_from_time_abs (timestamp),
+                              "reason",
+                              reason)));
+  /* For refunded coins, we are not charged deposit fees, so subtract those
+     again */
+  for (struct TransferQuery *tq = gorc->tq_head;
+       NULL != tq;
+       tq = tq->next)
+  {
+    if (0 ==
+        GNUNET_memcmp (&tq->coin_pub,
+                       coin_pub))
+    {
+      GNUNET_assert (0 <=
+                     TALER_amount_subtract (&gorc->deposit_fees_total,
+                                            &gorc->deposit_fees_total,
+                                            &tq->deposit_fee));
+    }
+  }
+  GNUNET_assert (0 <=
+                 TALER_amount_add (&gorc->refund_amount,
+                                   &gorc->refund_amount,
+                                   refund_amount));
+  gorc->refunded = true;
+}
+
+
+/**
+ * Function called with available wire details, to be added to
+ * the response.
+ *
+ * @param cls a `struct GetOrderRequestContext`
+ * @param wtid wire transfer subject of the wire transfer for the coin
+ * @param exchange_url base URL of the exchange that made the payment
+ * @param execution_time when was the payment made
+ * @param deposit_value contribution of the coin to the total wire transfer 
value
+ * @param deposit_fee deposit fee charged by the exchange for the coin
+ * @param transfer_confirmed did the merchant confirm that a wire transfer with
+ *        @a wtid over the total amount happened?
+ */
+static void
+process_transfer_details (void *cls,
+                          const struct TALER_WireTransferIdentifierRawP *wtid,
+                          const char *exchange_url,
+                          struct GNUNET_TIME_Absolute execution_time,
+                          const struct TALER_Amount *deposit_value,
+                          const struct TALER_Amount *deposit_fee,
+                          bool transfer_confirmed)
+{
+  struct GetOrderRequestContext *gorc = cls;
+  json_t *wire_details = gorc->wire_details;
+  struct TALER_Amount wired;
+
+  GNUNET_assert
+    (0 <= TALER_amount_subtract (&wired,
+                                 deposit_value,
+                                 deposit_fee));
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   wire_details,
+                   json_pack ("{s:o, s:s, s:o, s:o, s:b}",
+                              "wtid",
+                              GNUNET_JSON_from_data_auto (wtid),
+                              "exchange_url",
+                              exchange_url,
+                              "amount",
+                              TALER_JSON_from_amount (&wired),
+                              "execution_time",
+                              GNUNET_JSON_from_time_abs (execution_time),
+                              "confirmed",
+                              transfer_confirmed)));
+}
+
+
+/**
+ * Manages a GET /private/orders/ID call, checking the status of a payment and
+ * refunds and, if necessary, constructing the URL for a payment redirect URL.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc)
+{
+  struct GetOrderRequestContext *gorc = hc->ctx;
+  enum GNUNET_DB_QueryStatus qs;
+  bool paid;
+  bool wired;
+
+  if (NULL == gorc)
+  {
+    /* First time here, parse request and check order is known */
+    GNUNET_assert (NULL != hc->infix);
+    gorc = GNUNET_new (struct GetOrderRequestContext);
+    hc->cc = &gorc_cleanup;
+    hc->ctx = gorc;
+    gorc->sc.con = connection;
+    gorc->hc = hc;
+    gorc->wire_details = json_array ();
+    GNUNET_assert (NULL != gorc->wire_details);
+    gorc->refund_details = json_array ();
+    GNUNET_assert (NULL != gorc->refund_details);
+    gorc->wire_reports = json_array ();
+    GNUNET_assert (NULL != gorc->wire_reports);
+    gorc->session_id = MHD_lookup_connection_value (connection,
+                                                    MHD_GET_ARGUMENT_KIND,
+                                                    "session_id");
+    {
+      const char *long_poll_timeout_s;
+
+      long_poll_timeout_s = MHD_lookup_connection_value (connection,
+                                                         MHD_GET_ARGUMENT_KIND,
+                                                         "timeout_ms");
+      if (NULL != long_poll_timeout_s)
+      {
+        unsigned long long timeout;
+
+        if (1 != sscanf (long_poll_timeout_s,
+                         "%llu",
+                         &timeout))
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             TALER_EC_PARAMETER_MALFORMED,
+                                             "timeout must be non-negative 
number");
+        }
+        gorc->sc.long_poll_timeout
+          = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+                                                GNUNET_TIME_UNIT_MILLISECONDS,
+                                                timeout));
+      }
+      else
+      {
+        gorc->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS;
+      }
+    }
+
+    {
+      const char *transfer_s;
+
+      transfer_s = MHD_lookup_connection_value (connection,
+                                                MHD_GET_ARGUMENT_KIND,
+                                                "transfer");
+      if ( (NULL != transfer_s) &&
+           (0 == strcasecmp (transfer_s,
+                             "yes")) )
+        gorc->transfer_status_requested = true;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Starting GET /private/orders/%s processing with timeout %s\n",
+                hc->infix,
+                GNUNET_STRINGS_absolute_time_to_string (
+                  gorc->sc.long_poll_timeout));
+
+    TMH_db->preflight (TMH_db->cls);
+    qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                        hc->instance->settings.id,
+                                        hc->infix,
+                                        &gorc->contract_terms,
+                                        &gorc->order_serial);
+    if (0 > qs)
+    {
+      /* single, read-only SQL statements should never cause
+         serialization problems */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GET_ORDERS_DB_FETCH_CONTRACT_TERMS_ERROR,
+                                         "db error fetching contract terms");
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_GET_ORDERS_ORDER_NOT_FOUND,
+                                         "Did not find contract terms for 
order in DB");
+    }
+
+    /* extract the fulfillment URL and total amount from the contract terms! */
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_string ("fulfillment_url",
+                                 &gorc->fulfillment_url),
+        TALER_JSON_spec_amount ("amount",
+                                &gorc->contract_amount),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (gorc->contract_terms,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_INTERNAL_SERVER_ERROR,
+          TALER_EC_GET_ORDERS_DB_FETCH_CONTRACT_TERMS_ERROR,
+          "Merchant database error (contract terms corrupted)");
+      }
+      if (0 !=
+          strcasecmp (TMH_currency,
+                      gorc->contract_amount.currency))
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_INTERNAL_SERVER_ERROR,
+          TALER_EC_GET_ORDERS_DB_FETCH_CONTRACT_TERMS_ERROR,
+          "Merchant database error (contract terms in wrong currency)");
+      }
+    }
+    if (GNUNET_OK !=
+        TALER_JSON_hash (gorc->contract_terms,
+                         &gorc->h_contract_terms))
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GET_ORDERS_FAILED_COMPUTE_PROPOSAL_HASH,
+                                         "Failed to hash contract terms");
+    }
+  }
+  if (TALER_EC_NONE != gorc->wire_ec)
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       gorc->wire_hc,
+                                       gorc->wire_ec,
+                                       "Failed to obtain wire details");
+  }
+
+  GNUNET_assert (NULL != gorc->contract_terms);
+
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->lookup_payment_status (TMH_db->cls,
+                                      gorc->order_serial,
+                                      gorc->session_id,
+                                      &paid,
+                                      &wired);
+  if (0 >= qs)
+  {
+    /* single, read-only SQL statements should never cause
+       serialization problems, and the entry should exist as per above */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GET_ORDERS_DB_FETCH_PAYMENT_STATUS,
+                                       "DB error fetching payment status");
+  }
+  if ((! paid) &&
+      (NULL != gorc->session_id))
+  {
+    char *already_paid_order_id;
+
+    qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
+                                              hc->instance->settings.id,
+                                              gorc->fulfillment_url,
+                                              gorc->session_id,
+                                              &already_paid_order_id);
+    if (0 > qs)
+    {
+      /* single, read-only SQL statements should never cause
+         serialization problems, and the entry should exist as per above */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GET_ORDERS_DB_FETCH_PAYMENT_STATUS,
+                                         "DB error fetching payment status");
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    {
+      /* User did pay for this order, but under a different session; ask wallet
+         to switch order ID */
+      char *taler_pay_uri;
+      MHD_RESULT ret;
+
+      taler_pay_uri = TMH_make_taler_pay_uri (connection,
+                                              hc->infix,
+                                              gorc->session_id,
+                                              hc->instance->settings.id);
+      ret = TALER_MHD_reply_json_pack (connection,
+                                       MHD_HTTP_OK,
+                                       "{s:s, s:b, s:s}",
+                                       "taler_pay_uri",
+                                       taler_pay_uri,
+                                       "paid",
+                                       false,
+                                       "already_paid_order_id",
+                                       already_paid_order_id);
+      GNUNET_free (taler_pay_uri);
+      GNUNET_free (already_paid_order_id);
+      return ret;
+    }
+  }
+
+  if (paid &&
+      (! wired) &&
+      gorc->transfer_status_requested)
+  {
+    /* suspend connection, wait for exchange to check wire transfer status 
there */
+    gorc->transfer_status_requested = false;   /* only try ONCE */
+    TMH_db->lookup_deposits_by_order (TMH_db->cls,
+                                      gorc->order_serial,
+                                      &deposit_cb,
+                                      gorc);
+    if (NULL != gorc->tq_head)
+    {
+      GNUNET_CONTAINER_DLL_insert (gorc_head,
+                                   gorc_tail,
+                                   gorc);
+      gorc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+                                               &exchange_timeout_cb,
+                                               gorc);
+      MHD_suspend_connection (connection);
+      return MHD_YES;
+    }
+  }
+
+  if ( (! paid) &&
+       (0 != gorc->sc.long_poll_timeout.abs_value_us) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Suspending GET /private/orders/%s\n",
+                hc->infix);
+    TMH_long_poll_suspend (hc->infix,
+                           hc->instance,
+                           &gorc->sc,
+                           NULL);
+    return MHD_YES;
+  }
+
+  if (! paid)
+  {
+    /* User never paid for this order */
+    char *taler_pay_uri;
+    MHD_RESULT ret;
+
+    taler_pay_uri = TMH_make_taler_pay_uri (connection,
+                                            hc->infix,
+                                            gorc->session_id,
+                                            hc->instance->settings.id);
+    ret = TALER_MHD_reply_json_pack (connection,
+                                     MHD_HTTP_OK,
+                                     "{s:s, s:O, s:b}",
+                                     "taler_pay_uri",
+                                     taler_pay_uri,
+                                     "contract_terms",
+                                     gorc->contract_terms,
+                                     "paid",
+                                     false);
+    GNUNET_free (taler_pay_uri);
+    return ret;
+  }
+
+  /* Here we know the user DID pay, compute refunds... */
+
+  /* Accumulate refunds, if any. */
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_get_zero (TMH_currency,
+                                          &gorc->refund_amount));
+    qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
+                                          hc->instance->settings.id,
+                                          &gorc->h_contract_terms,
+                                          &process_refunds_cb,
+                                          gorc);
+  }
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GET_ORDERS_DB_FETCH_TRANSACTION_ERROR,
+                                       "Merchant database error");
+  }
+
+  /* Generate final reply, including wire details if we have them */
+  {
+    MHD_RESULT ret;
+
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_get_zero (TMH_currency,
+                                          &gorc->deposits_total));
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_get_zero (TMH_currency,
+                                          &gorc->deposit_fees_total));
+    qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
+                                                   gorc->order_serial,
+                                                   &process_transfer_details,
+                                                   gorc);
+    if (0 > qs)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+                                         "Merchant database error");
+    }
+
+    if (! wired)
+    {
+      /* we believe(d) the wire transfer did not happen yet, check if maybe
+         in light of new evidence it did */
+      struct TALER_Amount expect_total;
+
+      if (0 >
+          TALER_amount_subtract (&expect_total,
+                                 &gorc->contract_amount,
+                                 &gorc->refund_amount))
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_GET_ORDERS_CONTRACT_CONTENT_INVALID,
+                                           "Inconsistent contract terms in 
DB");
+      }
+      if (0 >
+          TALER_amount_subtract (&expect_total,
+                                 &expect_total,
+                                 &gorc->deposit_fees_total))
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_GET_ORDERS_CONTRACT_CONTENT_INVALID,
+                                           "Inconsistent contract terms in 
DB");
+      }
+      if (0 >=
+          TALER_amount_cmp (&expect_total,
+                            &gorc->deposits_total))
+      {
+        struct GNUNET_TIME_Absolute timestamp;
+
+        /* expect_total <= gorc->deposits_total: good: we got paid */
+        wired = true;
+        qs = TMH_db->mark_order_wired (TMH_db->cls,
+                                       gorc->order_serial);
+        GNUNET_break (qs >= 0); /* just warn if transaction failed */
+        {
+          struct GNUNET_JSON_Specification spec[] = {
+            GNUNET_JSON_spec_absolute_time ("timestamp",
+                                            &timestamp),
+            GNUNET_JSON_spec_end ()
+          };
+          enum GNUNET_GenericReturnValue res;
+
+          res = TALER_MHD_parse_internal_json_data (connection,
+                                                    gorc->contract_terms,
+                                                    spec);
+          if (GNUNET_YES != res)
+          {
+            GNUNET_break (0);
+            return res;
+          }
+        }
+        TMH_notify_order_change (hc->instance,
+                                 hc->infix,
+                                 true, /* paid */
+                                 false, /* technically unknown, but OK here */
+                                 true, /* wired */
+                                 timestamp,
+                                 gorc->order_serial);
+      }
+    }
+
+    ret = TALER_MHD_reply_json_pack (connection,
+                                     MHD_HTTP_OK,
+                                     "{s:o, s:I, s:I, s:o, s:O,"
+                                     " s:b, s:b, s:b, s:o, s:o, s:o}",
+                                     "wire_reports",
+                                     gorc->wire_reports,
+                                     "exchange_ec",
+                                     (json_int_t) gorc->exchange_ec,
+                                     "exchange_hc",
+                                     (json_int_t) gorc->exchange_hc,
+                                     "deposit_total",
+                                     TALER_JSON_from_amount (
+                                       &gorc->deposits_total),
+                                     "contract_terms",
+                                     gorc->contract_terms,
+                                     "paid",
+                                     true,
+                                     "refunded",
+                                     gorc->refunded,
+                                     "wired",
+                                     wired,
+                                     "refund_amount",
+                                     TALER_JSON_from_amount (
+                                       &gorc->refund_amount),
+                                     "wire_details",
+                                     gorc->wire_details,
+                                     "refund_details",
+                                     gorc->refund_details);
+    gorc->wire_details = NULL;
+    gorc->wire_reports = NULL;
+    gorc->refund_details = NULL;
+    return ret;
+  }
+}
diff --git a/src/backend/taler-merchant-httpd_config.h 
b/src/backend/taler-merchant-httpd_private-get-orders-ID.h
similarity index 54%
copy from src/backend/taler-merchant-httpd_config.h
copy to src/backend/taler-merchant-httpd_private-get-orders-ID.h
index 1a5dfd6..2b60310 100644
--- a/src/backend/taler-merchant-httpd_config.h
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2019 Taler Systems SA
+  (C) 2017, 2020 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,32 +14,28 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_config.h
- * @brief headers for /config handler
+ * @file backend/taler-merchant-httpd_private-get-orders-ID.h
+ * @brief headers for GET /private/orders/ID handler
+ * @author Christian Grothoff
  * @author Florian Dold
  */
-#ifndef TALER_MERCHANT_HTTPD_CONFIG_H
-#define TALER_MERCHANT_HTTPD_CONFIG_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_ORDERS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_ORDERS_ID_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 /**
- * Manages a /config call.
+ * Manages a GET /private/orders/ID call, checking the status of a payment and
+ * refunds and, if necessary, constructing the URL for a payment redirect URL.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_config (struct TMH_RequestHandler *rh,
-                   struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size,
-                   struct MerchantInstance *mi);
+TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-orders.c 
b/src/backend/taler-merchant-httpd_private-get-orders.c
new file mode 100644
index 0000000..608d276
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-orders.c
@@ -0,0 +1,497 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-orders.c
+ * @brief implement GET /orders
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-orders.h"
+
+
+/**
+ * A pending GET /orders request that is in long polling mode.
+ */
+struct TMH_PendingOrder
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct TMH_PendingOrder *prev;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct TMH_PendingOrder *next;
+
+  /**
+   * Which connection was suspended.
+   */
+  struct MHD_Connection *con;
+
+  /**
+   * Associated heap node.
+   */
+  struct GNUNET_CONTAINER_HeapNode *hn;
+
+  /**
+   * Which instance is this client polling? This also defines
+   * which DLL this struct is part of.
+   */
+  struct TMH_MerchantInstance *mi;
+
+  /**
+   * At what time does this request expire? If set in the future, we
+   * may wait this long for a payment to arrive before responding.
+   */
+  struct GNUNET_TIME_Absolute long_poll_timeout;
+
+  /**
+   * Array where we append matching orders. Must be
+   * json_decref()'ed when done with the `struct TMH_PendingOrder`!
+   */
+  json_t *pa;
+
+  /**
+   * Filter to apply.
+   */
+  struct TALER_MERCHANTDB_OrderFilter of;
+};
+
+
+/**
+ * Task to timeout pending orders.
+ */
+static struct GNUNET_SCHEDULER_Task *order_timeout_task;
+
+/**
+ * Heap for orders in long polling awaiting timeout.
+ */
+static struct GNUNET_CONTAINER_Heap *order_timeout_heap;
+
+
+/**
+ * We are shutting down (or an instance is being deleted), force resume of all
+ * GET /orders requests.
+ *
+ * @param mi instance to force resuming for
+ */
+void
+TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
+{
+  struct TMH_PendingOrder *po;
+
+  while (NULL != (po = mi->po_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (mi->po_head,
+                                 mi->po_tail,
+                                 po);
+    GNUNET_assert (po ==
+                   GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
+    MHD_resume_connection (po->con);
+    json_decref (po->pa);
+    GNUNET_free (po);
+  }
+  if (NULL != order_timeout_task)
+  {
+    GNUNET_SCHEDULER_cancel (order_timeout_task);
+    order_timeout_task = NULL;
+  }
+  if (NULL != order_timeout_heap)
+  {
+    GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
+    order_timeout_heap = NULL;
+  }
+}
+
+
+/**
+ * Task run to trigger timeouts on GET /orders requests with long polling.
+ *
+ * @param cls unused
+ */
+static void
+order_timeout (void *cls)
+{
+  struct TMH_PendingOrder *po;
+  struct TMH_MerchantInstance *mi;
+
+  (void) cls;
+  order_timeout_task = NULL;
+  while (1)
+  {
+    po = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
+    if (NULL == po)
+    {
+      /* release data structure, we don't need it right now */
+      GNUNET_CONTAINER_heap_destroy (order_timeout_heap);
+      order_timeout_heap = NULL;
+      return;
+    }
+    if  (0 !=
+         GNUNET_TIME_absolute_get_remaining (
+           po->long_poll_timeout).rel_value_us)
+      break;
+    GNUNET_assert (po ==
+                   GNUNET_CONTAINER_heap_remove_root (order_timeout_heap));
+    po->hn = NULL;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Resuming long polled job due to timeout\n");
+    mi = po->mi;
+    GNUNET_CONTAINER_DLL_remove (mi->po_head,
+                                 mi->po_tail,
+                                 po);
+    json_decref (po->pa);
+    MHD_resume_connection (po->con);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    GNUNET_free (po);
+  }
+  order_timeout_task = GNUNET_SCHEDULER_add_at (po->long_poll_timeout,
+                                                &order_timeout,
+                                                NULL);
+}
+
+
+/**
+ * Cleanup our "context", where we stored the JSON array
+ * we are building for the response.
+ *
+ * @param ctx context to clean up, must be a `json_t *`
+ */
+static void
+json_cleanup (void *ctx)
+{
+  json_t *j = ctx;
+
+  json_decref (j);
+}
+
+
+/**
+ * Add order details to our JSON array.
+ *
+ * @param[in,out] cls a `json_t *` JSON array to build
+ * @param order_id ID of the order
+ * @param order_serial serial ID of the order
+ * @param creation_time when was the order created
+ */
+static void
+add_order (void *cls,
+           const char *order_id,
+           uint64_t order_serial,
+           struct GNUNET_TIME_Absolute creation_time)
+{
+  json_t *pa = cls;
+
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   pa,
+                   json_pack (
+                     "{s:s, s:I, s:o}",
+                     "order_id",
+                     order_id,
+                     "row_id",
+                     (json_int_t) order_serial,
+                     "timestamp",
+                     GNUNET_JSON_from_time_abs (creation_time))));
+}
+
+
+/**
+ * There has been a change or addition of a new @a order_id.  Wake up
+ * long-polling clients that may have been waiting for this event.
+ *
+ * @param mi the instance where the order changed
+ * @param order_id the order that changed
+ * @param paid is the order paid by the customer?
+ * @param refunded was the order refunded?
+ * @param wire was the merchant paid via wire transfer?
+ * @param date execution date of the order
+ * @param order_serial_id serial ID of the order in the database
+ */
+void
+TMH_notify_order_change (struct TMH_MerchantInstance *mi,
+                         const char *order_id,
+                         bool paid,
+                         bool refunded,
+                         bool wired,
+                         struct GNUNET_TIME_Absolute date,
+                         uint64_t order_serial_id)
+{
+  struct TMH_PendingOrder *pn;
+
+  for (struct TMH_PendingOrder *po = mi->po_head;
+       NULL != po;
+       po = pn)
+  {
+    pn = po->next;
+    if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) == paid) ||
+              (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
+            ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) == refunded) ||
+              (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
+            ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) == wired) ||
+              (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
+      continue;
+    if (po->of.delta > 0)
+    {
+      if (order_serial_id < po->of.start_row)
+        continue;
+      if (date.abs_value_us < po->of.date.abs_value_us)
+        continue;
+      po->of.delta--;
+    }
+    else
+    {
+      if (order_serial_id > po->of.start_row)
+        continue;
+      if (date.abs_value_us > po->of.date.abs_value_us)
+        continue;
+      po->of.delta++;
+    }
+    add_order (po->pa,
+               order_id,
+               order_serial_id,
+               date);
+    GNUNET_CONTAINER_DLL_remove (mi->po_head,
+                                 mi->po_tail,
+                                 po);
+    GNUNET_assert (po ==
+                   GNUNET_CONTAINER_heap_remove_node (po->hn));
+    MHD_resume_connection (po->con);
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    json_decref (po->pa);
+    GNUNET_free (po);
+  }
+}
+
+
+/**
+ * Handle a GET "/orders" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_orders (const struct TMH_RequestHandler *rh,
+                        struct MHD_Connection *connection,
+                        struct TMH_HandlerContext *hc)
+{
+  json_t *pa;
+  enum GNUNET_DB_QueryStatus qs;
+  struct TALER_MERCHANTDB_OrderFilter of;
+
+  if (NULL != hc->ctx)
+  {
+    /* resumed from long-polling, return answer we already have
+       in 'hc->ctx' */
+    return TALER_MHD_reply_json_pack (connection,
+                                      MHD_HTTP_OK,
+                                      "{s:O}",
+                                      "orders", hc->ctx);
+  }
+
+  if (! (TALER_arg_to_yna (connection,
+                           "paid",
+                           TALER_EXCHANGE_YNA_ALL,
+                           &of.paid)) )
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "paid");
+  if (! (TALER_arg_to_yna (connection,
+                           "refunded",
+                           TALER_EXCHANGE_YNA_ALL,
+                           &of.refunded)) )
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "refunded");
+  if (! (TALER_arg_to_yna (connection,
+                           "wired",
+                           TALER_EXCHANGE_YNA_ALL,
+                           &of.wired)) )
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "wired");
+  {
+    const char *start_row_str;
+
+    start_row_str = MHD_lookup_connection_value (connection,
+                                                 MHD_GET_ARGUMENT_KIND,
+                                                 "start");
+    if (NULL == start_row_str)
+    {
+      of.start_row = INT64_MAX;
+    }
+    else
+    {
+      char dummy[2];
+      unsigned long long ull;
+
+      if (1 !=
+          sscanf (start_row_str,
+                  "%llu%1s",
+                  &ull,
+                  dummy))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "date");
+      of.start_row = (uint64_t) ull;
+    }
+  }
+  {
+    const char *delta_str;
+
+    delta_str = MHD_lookup_connection_value (connection,
+                                             MHD_GET_ARGUMENT_KIND,
+                                             "delta");
+    if (NULL == delta_str)
+    {
+      of.delta = -20;
+    }
+    else
+    {
+      char dummy[2];
+      long long ll;
+
+      if (1 !=
+          sscanf (delta_str,
+                  "%lld%1s",
+                  &ll,
+                  dummy))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "delta");
+      of.delta = (uint64_t) ll;
+    }
+  }
+  {
+    const char *date_str;
+
+    date_str = MHD_lookup_connection_value (connection,
+                                            MHD_GET_ARGUMENT_KIND,
+                                            "date");
+    if (NULL == date_str)
+    {
+      if (of.delta > 0)
+        of.date = GNUNET_TIME_UNIT_ZERO_ABS;
+      else
+        of.date = GNUNET_TIME_UNIT_FOREVER_ABS;
+    }
+    else
+    {
+      if (GNUNET_OK !=
+          GNUNET_STRINGS_fancy_time_to_absolute (date_str,
+                                                 &of.date))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "date");
+    }
+  }
+  {
+    const char *timeout_ms_str;
+
+    timeout_ms_str = MHD_lookup_connection_value (connection,
+                                                  MHD_GET_ARGUMENT_KIND,
+                                                  "timeout_ms");
+    if (NULL == timeout_ms_str)
+    {
+      of.timeout = GNUNET_TIME_UNIT_ZERO;
+    }
+    else
+    {
+      char dummy[2];
+      unsigned long long ull;
+
+      if (1 !=
+          sscanf (timeout_ms_str,
+                  "%lld%1s",
+                  &ull,
+                  dummy))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "timeout_ms");
+      of.timeout = GNUNET_TIME_relative_multiply 
(GNUNET_TIME_UNIT_MILLISECONDS,
+                                                  ull);
+    }
+  }
+
+  pa = json_array ();
+  GNUNET_assert (NULL != pa);
+  qs = TMH_db->lookup_orders (TMH_db->cls,
+                              hc->instance->settings.id,
+                              &of,
+                              &add_order,
+                              pa);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    json_decref (pa);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_ORDERS_GET_DB_LOOKUP_ERROR,
+                                       "failed to lookup orders in database");
+  }
+  if ( (0 == qs) &&
+       (of.timeout.rel_value_us > 0) )
+  {
+    struct TMH_MerchantInstance *mi = hc->instance;
+    struct TMH_PendingOrder *po;
+
+    /* setup timeout heap (if not yet exists) */
+    if (NULL == order_timeout_heap)
+      order_timeout_heap
+        = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
+    hc->ctx = pa;
+    hc->cc = &json_cleanup;
+    po = GNUNET_new (struct TMH_PendingOrder);
+    po->mi = mi;
+    po->con = connection;
+    po->pa = json_incref (pa);
+    po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap,
+                                           po,
+                                           po->long_poll_timeout.abs_value_us);
+    po->long_poll_timeout = GNUNET_TIME_relative_to_absolute (of.timeout);
+    po->of = of;
+    GNUNET_CONTAINER_DLL_insert (mi->po_head,
+                                 mi->po_tail,
+                                 po);
+    MHD_suspend_connection (connection);
+    /* start timeout task */
+    po = GNUNET_CONTAINER_heap_peek (order_timeout_heap);
+    if (NULL != order_timeout_task)
+      GNUNET_SCHEDULER_cancel (order_timeout_task);
+    order_timeout_task = GNUNET_SCHEDULER_add_at (po->long_poll_timeout,
+                                                  &order_timeout,
+                                                  NULL);
+    return MHD_YES;
+  }
+  return TALER_MHD_reply_json_pack (connection,
+                                    MHD_HTTP_OK,
+                                    "{s:o}",
+                                    "orders", pa);
+}
+
+
+/* end of taler-merchant-httpd_private-get-orders.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-orders.h 
b/src/backend/taler-merchant-httpd_private-get-orders.h
new file mode 100644
index 0000000..c6f6fbb
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-orders.h
@@ -0,0 +1,74 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-orders.h
+ * @brief implement GET /orders
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_ORDERS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_ORDERS_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/orders" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_orders (const struct TMH_RequestHandler *rh,
+                        struct MHD_Connection *connection,
+                        struct TMH_HandlerContext *hc);
+
+
+/**
+ * There has been a change or addition of a new @a order_id.  Wake up
+ * long-polling clients that may have been waiting for this event.
+ *
+ * @param mi the instance where the order changed
+ * @param order_id the order that changed
+ * @param paid is the order paid by the customer?
+ * @param refunded was the order refunded?
+ * @param wire was the merchant paid via wire transfer?
+ * @param date execution date of the order
+ * @param order_serial_id serial ID of the order in the database
+ */
+void
+TMH_notify_order_change (struct TMH_MerchantInstance *mi,
+                         const char *order_id,
+                         bool paid,
+                         bool refunded,
+                         bool wired,
+                         struct GNUNET_TIME_Absolute date,
+                         uint64_t order_serial_id);
+
+
+/**
+ * We are shutting down (or an instance is being deleted), force resume of all
+ * GET /orders requests.
+ *
+ * @param mi instance to force resuming for
+ */
+void
+TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi);
+
+
+/* end of taler-merchant-httpd_private-get-orders.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-products-ID.c 
b/src/backend/taler-merchant-httpd_private-get-products-ID.c
new file mode 100644
index 0000000..611de7e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-products-ID.c
@@ -0,0 +1,98 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-products-ID.c
+ * @brief implement GET /products/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-products-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/products/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_products_ID (const struct TMH_RequestHandler *rh,
+                             struct MHD_Connection *connection,
+                             struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  struct TALER_MERCHANTDB_ProductDetails pd;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (NULL != mi);
+  qs = TMH_db->lookup_product (TMH_db->cls,
+                               mi->settings.id,
+                               hc->infix,
+                               &pd);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GET_PRODUCTS_DB_LOOKUP_ERROR,
+                                       "failed to lookup products in 
database");
+  }
+  {
+    json_t *reply;
+
+    reply = json_pack (
+      "{s:s, s:s, s:o, s:o, s:I,"
+      " s:I, s:I, s:o, s:o, s:o}",
+      "description",
+      pd.description,
+      "unit",
+      pd.unit,
+      "price",
+      TALER_JSON_from_amount (&pd.price),
+      "taxes",
+      pd.taxes,
+      "total_stock",
+      (UINT64_MAX == pd.total_stock)
+      ? (json_int_t) -1
+      : (json_int_t) pd.total_stock,
+      /* end of first group of 5 */
+      "total_sold",
+      (json_int_t) pd.total_sold,
+      "total_lost",
+      (json_int_t) pd.total_lost,
+      "description_i18n",
+      pd.description_i18n,
+      "address",
+      pd.address,
+      "image",
+      pd.image);
+    GNUNET_free (pd.description);
+    GNUNET_free (pd.unit);
+    if (0 != pd.next_restock.abs_value_us)
+      json_object_set_new (reply,
+                           "next_restock",
+                           GNUNET_JSON_from_time_abs (pd.next_restock));
+    return TALER_MHD_reply_json (connection,
+                                 reply,
+                                 MHD_HTTP_OK);
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-get-products-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-products-ID.h 
b/src/backend/taler-merchant-httpd_private-get-products-ID.h
new file mode 100644
index 0000000..d6e9c7a
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-products-ID.h
@@ -0,0 +1,41 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-products-ID.h
+ * @brief implement GET /products/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_PRODUCTS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_PRODUCTS_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/products/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_products_ID (const struct TMH_RequestHandler *rh,
+                             struct MHD_Connection *connection,
+                             struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-products-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-products.c 
b/src/backend/taler-merchant-httpd_private-get-products.c
new file mode 100644
index 0000000..00b0463
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-products.c
@@ -0,0 +1,85 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-products.c
+ * @brief implement GET /products
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-products.h"
+
+
+/**
+ * Add product details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param product_id ID of the product
+ */
+static void
+add_product (void *cls,
+             const char *product_id)
+{
+  json_t *pa = cls;
+
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   pa,
+                   json_pack (
+                     "{s:s}",
+                     "product_id",
+                     product_id)));
+}
+
+
+/**
+ * Handle a GET "/products" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_products (const struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          struct TMH_HandlerContext *hc)
+{
+  json_t *pa;
+  enum GNUNET_DB_QueryStatus qs;
+
+  pa = json_array ();
+  GNUNET_assert (NULL != pa);
+  qs = TMH_db->lookup_products (TMH_db->cls,
+                                hc->instance->settings.id,
+                                &add_product,
+                                pa);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    json_decref (pa);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GET_PRODUCTS_DB_LOOKUP_ERROR,
+                                       "failed to lookup products in 
database");
+  }
+  return TALER_MHD_reply_json_pack (connection,
+                                    MHD_HTTP_OK,
+                                    "{s:o}",
+                                    "products", pa);
+}
+
+
+/* end of taler-merchant-httpd_private-get-products.c */
diff --git a/src/backend/taler-merchant-httpd_refund_lookup.h 
b/src/backend/taler-merchant-httpd_private-get-products.h
similarity index 53%
copy from src/backend/taler-merchant-httpd_refund_lookup.h
copy to src/backend/taler-merchant-httpd_private-get-products.h
index 24495da..dbfd59e 100644
--- a/src/backend/taler-merchant-httpd_refund_lookup.h
+++ b/src/backend/taler-merchant-httpd_private-get-products.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015, 2016, 2017 Taler Systems SA
+  (C) 2019, 2020 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
@@ -13,36 +13,29 @@
   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_refund_lookup.h
- * @brief
- * @author Marcello Stanisci
+ * @file backend/taler-merchant-httpd_private-get-products.h
+ * @brief implement GET /products
+ * @author Christian Grothoff
  */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_PRODUCTS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_PRODUCTS_H
 
-#ifndef TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
-#define TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
-#include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Return refund situation about a contract.
+ * Handle a GET "/products" 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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
+TMH_private_get_products (const struct TMH_RequestHandler *rh,
                           struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi);
+                          struct TMH_HandlerContext *hc);
 
+/* end of taler-merchant-httpd_private-get-products.h */
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c 
b/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
new file mode 100644
index 0000000..1945524
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
@@ -0,0 +1,217 @@
+/*
+  This file is part of TALER
+  (C) 2018, 2020 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_private-get-reserves-ID.c
+ * @brief implement GET /reserves/$RESERVE_PUB endpoint
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_private-get-reserves-ID.h"
+
+
+/**
+ * Closure for handle_reserve_details().
+ */
+struct GetReserveContext
+{
+  /**
+   * Connection we are handling.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Value to return from the callback.
+   */
+  MHD_RESULT res;
+
+  /**
+   * Should we return details about tips?
+   */
+  bool tips;
+};
+
+
+/**
+ * Callback with reserve details.
+ *
+ * @param cls closure with a `struct GetReserveContext`
+ * @param creation_time time when the reserve was setup
+ * @param expiration_time time when the reserve will be closed by the exchange
+ * @param merchant_initial_amount initial amount that the merchant claims to 
have filled the
+ *           reserve with
+ * @param exchange_initial_amount initial amount that the exchange claims to 
have received
+ * @param picked_up_amount total of tips that were picked up from this reserve
+ * @param committed_amount total of tips that the merchant committed to, but 
that were not
+ *           picked up yet
+ * @param active true if the reserve is still active (we have the private key)
+ * @param tips_length length of the @a tips array
+ * @param tips information about the tips created by this reserve
+ */
+static void
+handle_reserve_details (void *cls,
+                        struct GNUNET_TIME_Absolute creation_time,
+                        struct GNUNET_TIME_Absolute expiration_time,
+                        const struct TALER_Amount *merchant_initial_amount,
+                        const struct TALER_Amount *exchange_initial_amount,
+                        const struct TALER_Amount *picked_up_amount,
+                        const struct TALER_Amount *committed_amount,
+                        bool active,
+                        unsigned int tips_length,
+                        const struct TALER_MERCHANTDB_TipDetails *tips)
+{
+  struct GetReserveContext *ctx = cls;
+  json_t *tips_json;
+  struct GNUNET_TIME_Absolute creation_time_round = creation_time;
+  struct GNUNET_TIME_Absolute expiration_time_round = expiration_time;
+
+  GNUNET_TIME_round_abs (&creation_time_round);
+  GNUNET_TIME_round_abs (&expiration_time_round);
+
+  if (NULL != tips)
+  {
+    tips_json = json_array ();
+    GNUNET_assert (NULL != tips_json);
+
+    for (unsigned int i = 0; i<tips_length; i++)
+    {
+      GNUNET_assert (0 ==
+                     json_array_append_new (
+                       tips_json,
+                       json_pack ("{s:o,s:o,s:s}",
+                                  "tip_id",
+                                  GNUNET_JSON_from_data_auto (
+                                    &tips[i].tip_id),
+                                  "total_amount",
+                                  TALER_JSON_from_amount (
+                                    &tips[i].total_amount),
+                                  "reason",
+                                  tips[i].reason)));
+    }
+  }
+  else
+  {
+    tips_json = NULL;
+  }
+  ctx->res = TALER_MHD_reply_json_pack (
+    ctx->connection,
+    MHD_HTTP_OK,
+    "{s:o, s:o, s:o, s:o, s:o, s:o, s:o?, s:b}",
+    "creation_time", GNUNET_JSON_from_time_abs (creation_time_round),
+    "expiration_time", GNUNET_JSON_from_time_abs (expiration_time_round),
+    "merchant_initial_amount", TALER_JSON_from_amount 
(merchant_initial_amount),
+    "exchange_initial_amount", TALER_JSON_from_amount 
(exchange_initial_amount),
+    "pickup_amount", TALER_JSON_from_amount (picked_up_amount),
+    "committed_amount", TALER_JSON_from_amount (committed_amount),
+    "tips", tips_json,
+    "active", active);
+}
+
+
+/**
+ * Manages a GET /reserves/$RESERVE_PUB call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_reserves_ID (const struct TMH_RequestHandler *rh,
+                             struct MHD_Connection *connection,
+                             struct TMH_HandlerContext *hc)
+{
+  struct TALER_ReservePublicKeyP reserve_pub;
+  bool tips;
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (hc->infix,
+                                     strlen (hc->infix),
+                                     &reserve_pub,
+                                     sizeof (reserve_pub)))
+  {
+    /* tip_id has wrong encoding */
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "reserve_pub malformed");
+  }
+  {
+    const char *tstr;
+
+    tstr = MHD_lookup_connection_value (connection,
+                                        MHD_GET_ARGUMENT_KIND,
+                                        "tips");
+    tips = (NULL != tstr)
+           ? 0 == strcasecmp (tstr, "yes")
+           : false;
+  }
+  {
+    struct GetReserveContext ctx = {
+      .connection = connection,
+      .tips = tips
+    };
+    enum GNUNET_DB_QueryStatus qs;
+
+    TMH_db->preflight (TMH_db->cls);
+    qs = TMH_db->lookup_reserve (TMH_db->cls,
+                                 hc->instance->settings.id,
+                                 &reserve_pub,
+                                 tips,
+                                 &handle_reserve_details,
+                                 &ctx);
+    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 TALER_MHD_reply_with_error (connection,
+                                         response_code,
+                                         ec,
+                                         "Could not determine exchange URL for 
the given tip id");
+    }
+    return ctx.res;
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-get-reserves-ID.c */
diff --git a/src/backend/taler-merchant-httpd_tip-query.h 
b/src/backend/taler-merchant-httpd_private-get-reserves-ID.h
similarity index 57%
copy from src/backend/taler-merchant-httpd_tip-query.h
copy to src/backend/taler-merchant-httpd_private-get-reserves-ID.h
index 3123486..4b9fa06 100644
--- a/src/backend/taler-merchant-httpd_tip-query.h
+++ b/src/backend/taler-merchant-httpd_private-get-reserves-ID.h
@@ -14,32 +14,26 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_tip-query.h
+ * @file backend/taler-merchant-httpd_private-get-reserves-ID.h
  * @brief headers for /tip-query handler
  * @author Florian Dold
  */
-#ifndef TALER_MERCHANT_HTTPD_TIP_QUERY_H
-#define TALER_MERCHANT_HTTPD_TIP_QUERY_H
+#ifndef TALER_MERCHANT_HTTPD_GET_RESERVES_ID_H
+#define TALER_MERCHANT_HTTPD_GET_RESERVES_ID_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 /**
- * Manages a /tip-query call.
+ * Manages a GET /reserves/$RESERVE_PUB call.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_tip_query (struct TMH_RequestHandler *rh,
-                      struct MHD_Connection *connection,
-                      void **connection_cls,
-                      const char *upload_data,
-                      size_t *upload_data_size,
-                      struct MerchantInstance *mi);
+TMH_private_get_reserves_ID (const struct TMH_RequestHandler *rh,
+                             struct MHD_Connection *connection,
+                             struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-reserves.c 
b/src/backend/taler-merchant-httpd_private-get-reserves.c
new file mode 100644
index 0000000..976d71c
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-reserves.c
@@ -0,0 +1,150 @@
+/*
+  This file is part of TALER
+  (C) 2019, 2020 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_private-get-reserves.c
+ * @brief implement GET /reserves
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_private-get-reserves.h"
+
+
+/**
+ * Add reserve details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param reserve_pub public key of the reserve
+ * @param creation_time time when the reserve was setup
+ * @param expiration_time time when the reserve will be closed by the exchange
+ * @param merchant_initial_amount initial amount that the merchant claims to 
have filled the
+ *           reserve with
+ * @param exchange_initial_amount initial amount that the exchange claims to 
have received
+ * @param pickup_amount total of tips that were picked up from this reserve
+ * @param committed_amount total of tips that the merchant committed to, but 
that were not
+ *           picked up yet
+ * @param active  true if the reserve is still active (we have the private key)
+ */
+static void
+add_reserve (void *cls,
+             const struct TALER_ReservePublicKeyP *reserve_pub,
+             struct GNUNET_TIME_Absolute creation_time,
+             struct GNUNET_TIME_Absolute expiration_time,
+             const struct TALER_Amount *merchant_initial_amount,
+             const struct TALER_Amount *exchange_initial_amount,
+             const struct TALER_Amount *pickup_amount,
+             const struct TALER_Amount *committed_amount,
+             bool active)
+{
+  json_t *pa = cls;
+  struct GNUNET_TIME_Absolute creation_time_round = creation_time;
+  struct GNUNET_TIME_Absolute expiration_time_round = expiration_time;
+
+  GNUNET_TIME_round_abs (&creation_time_round);
+  GNUNET_TIME_round_abs (&expiration_time_round);
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   pa,
+                   json_pack (
+                     "{s:o,s:o,s:o,"
+                     " s:o,s:o,s:o,s:o,"
+                     " s:b}",
+                     "reserve_pub",
+                     GNUNET_JSON_from_data_auto (reserve_pub),
+                     "creation_time",
+                     GNUNET_JSON_from_time_abs (creation_time_round),
+                     "expiration_time",
+                     GNUNET_JSON_from_time_abs (expiration_time_round),
+                     "merchant_initial_amount",
+                     TALER_JSON_from_amount (merchant_initial_amount),
+                     "exchange_initial_amount",
+                     TALER_JSON_from_amount (exchange_initial_amount),
+                     "pickup_amount",
+                     TALER_JSON_from_amount (pickup_amount),
+                     "committed_amount",
+                     TALER_JSON_from_amount (committed_amount),
+                     "active",
+                     active)));
+}
+
+
+/**
+ * Handle a GET "/reserves" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_reserves (const struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          struct TMH_HandlerContext *hc)
+{
+  json_t *ra;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_TIME_Absolute created_after = { 0 };
+  enum TALER_EXCHANGE_YesNoAll active;
+  enum TALER_EXCHANGE_YesNoAll failures;
+
+  if (! (TALER_arg_to_yna (connection,
+                           "active",
+                           TALER_EXCHANGE_YNA_ALL,
+                           &active)) )
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "active");
+  }
+
+  if (! (TALER_arg_to_yna (connection,
+                           "failures",
+                           TALER_EXCHANGE_YNA_ALL,
+                           &failures)) )
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "failures");
+  }
+
+  ra = json_array ();
+  GNUNET_assert (NULL != ra);
+  qs = TMH_db->lookup_reserves (TMH_db->cls,
+                                hc->instance->settings.id,
+                                created_after,
+                                active,
+                                failures,
+                                &add_reserve,
+                                ra);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    json_decref (ra);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GET_RESERVES_DB_LOOKUP_ERROR,
+                                       "failed to lookup reserves in 
database");
+  }
+  return TALER_MHD_reply_json_pack (connection,
+                                    MHD_HTTP_OK,
+                                    "{s:o}",
+                                    "reserves", ra);
+}
+
+
+/* end of taler-merchant-httpd_private-get-reserves.c */
diff --git a/src/backend/taler-merchant-httpd_refund_lookup.h 
b/src/backend/taler-merchant-httpd_private-get-reserves.h
similarity index 53%
rename from src/backend/taler-merchant-httpd_refund_lookup.h
rename to src/backend/taler-merchant-httpd_private-get-reserves.h
index 24495da..46d21ec 100644
--- a/src/backend/taler-merchant-httpd_refund_lookup.h
+++ b/src/backend/taler-merchant-httpd_private-get-reserves.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015, 2016, 2017 Taler Systems SA
+  (C) 2019, 2020 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
@@ -13,36 +13,29 @@
   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_refund_lookup.h
- * @brief
- * @author Marcello Stanisci
+ * @file backend/taler-merchant-httpd_private-get-reserves.h
+ * @brief implement GET /reserves
+ * @author Christian Grothoff
  */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_RESERVES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_RESERVES_H
 
-#ifndef TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
-#define TALER_MERCHANT_HTTPD_REFUND_LOOKUP_H
-#include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Return refund situation about a contract.
+ * Handle a GET "/reserves" 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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
+TMH_private_get_reserves (const struct TMH_RequestHandler *rh,
                           struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi);
+                          struct TMH_HandlerContext *hc);
 
+/* end of taler-merchant-httpd_private-get-reserves.h */
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-tips-ID.c 
b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
new file mode 100644
index 0000000..7c70a68
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
@@ -0,0 +1,159 @@
+/*
+  This file is part of TALER
+  (C) 2017-2020 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_get-tips-ID.c
+ * @brief implementation of a GET /tips/ID 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_exchanges.h"
+
+
+/**
+ * Manages a GET /tips/$ID call, returning the status of a tip.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
+                         struct MHD_Connection *connection,
+                         struct TMH_HandlerContext *hc)
+{
+  struct GNUNET_HashCode tip_id;
+  struct TALER_Amount total_authorized;
+  struct TALER_Amount total_picked_up;
+  char *reason;
+  struct GNUNET_TIME_Absolute expiration;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  unsigned int pickups_length = 0;
+  struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
+  enum GNUNET_DB_QueryStatus qs;
+  bool fpu;
+  json_t *pickups_json = NULL;
+
+  GNUNET_assert (NULL != hc->infix);
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_hash_from_string (hc->infix,
+                                      &tip_id))
+  {
+    /* tip_id has wrong encoding */
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "tip_id malformed");
+  }
+  {
+    const char *pstr;
+
+    pstr = MHD_lookup_connection_value (connection,
+                                        MHD_GET_ARGUMENT_KIND,
+                                        "pickups");
+    fpu = (NULL != pstr)
+          ? 0 == strcasecmp (pstr, "yes")
+          : false;
+  }
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->lookup_tip_details (TMH_db->cls,
+                                   hc->instance->settings.id,
+                                   &tip_id,
+                                   fpu,
+                                   &total_authorized,
+                                   &total_picked_up,
+                                   &reason,
+                                   &expiration,
+                                   &reserve_pub,
+                                   &pickups_length,
+                                   &pickups);
+  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 TALER_MHD_reply_with_error (connection,
+                                       response_code,
+                                       ec,
+                                       "Could not determine exchange URL for 
the given tip id");
+  }
+  if (fpu)
+  {
+    pickups_json = json_array ();
+    GNUNET_assert (NULL != pickups_json);
+    for (unsigned int i = 0; i<pickups_length; i++)
+    {
+      json_array_append_new (
+        pickups_json,
+        json_pack ("{s:o,s:I,s:o}",
+                   "pickup_id",
+                   GNUNET_JSON_from_data_auto (&pickups[i].pickup_id),
+                   "num_planchets",
+                   (json_int_t) pickups[i].num_planchets,
+                   "requested_amount",
+                   TALER_JSON_from_amount (&pickups[i].requested_amount)));
+    }
+  }
+  GNUNET_array_grow (pickups,
+                     pickups_length,
+                     0);
+  {
+    struct GNUNET_TIME_Absolute expiration_round = expiration;
+    MHD_RESULT ret;
+
+    GNUNET_TIME_round_abs (&expiration_round);
+
+    ret = TALER_MHD_reply_json_pack (
+      connection,
+      MHD_HTTP_OK,
+      "{s:o, s:o, s:s, s:o, s:o, s:o?}",
+      "total_authorized", TALER_JSON_from_amount (&total_authorized),
+      "total_picked_up", TALER_JSON_from_amount (&total_picked_up),
+      "reason", reason,
+      "expiration", GNUNET_JSON_from_time_abs (expiration_round),
+      "reserve_pub", GNUNET_JSON_from_data_auto (&reserve_pub),
+      "pickups", pickups_json);
+    GNUNET_free (reason);
+    return ret;
+  }
+}
diff --git a/src/backend/taler-merchant-httpd_poll-payment.h 
b/src/backend/taler-merchant-httpd_private-get-tips-ID.h
similarity index 55%
rename from src/backend/taler-merchant-httpd_poll-payment.h
rename to src/backend/taler-merchant-httpd_private-get-tips-ID.h
index ac13c4a..5c428c9 100644
--- a/src/backend/taler-merchant-httpd_poll-payment.h
+++ b/src/backend/taler-merchant-httpd_private-get-tips-ID.h
@@ -14,34 +14,28 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_poll-payment.h
- * @brief headers for /public/poll-payment handler
+ * @file backend/taler-merchant-httpd_get-tips-ID.h
+ * @brief headers for GET /tips/ID handler
  * @author Christian Grothoff
- * @author Florian Dold
  */
-#ifndef TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
-#define TALER_MERCHANT_HTTPD_POLL_PAYMENT_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_ID_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
+
 /**
- * Manages a /public/poll-payment call, checking the status
- * of a payment.
+ * Manages a GET /tips/$ID call, returning the status of the tip.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_poll_payment (struct TMH_RequestHandler *rh,
+TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
                          struct MHD_Connection *connection,
-                         void **connection_cls,
-                         const char *upload_data,
-                         size_t *upload_data_size,
-                         struct MerchantInstance *mi);
+                         struct TMH_HandlerContext *hc);
+
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-tips.c 
b/src/backend/taler-merchant-httpd_private-get-tips.c
new file mode 100644
index 0000000..8aff670
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-tips.c
@@ -0,0 +1,161 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-get-tips.c
+ * @brief implementation of a GET /private/tips handler
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-tips.h"
+#include <taler/taler_json_lib.h>
+
+/**
+ * Add tip details to our JSON array.
+ *
+ * @param[in,out] cls a `json_t *` JSON array to build
+ * @param row_id row number of the tip
+ * @param tip_id ID of the tip
+ * @param amount the amount of the tip
+ */
+static void
+add_tip (void *cls,
+         uint64_t row_id,
+         struct GNUNET_HashCode tip_id,
+         struct TALER_Amount amount)
+{
+  json_t *pa = cls;
+
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   pa,
+                   json_pack (
+                     "{s:I, s:o, s:o}",
+                     "row_id",
+                     row_id,
+                     "tip_id",
+                     GNUNET_JSON_from_data_auto (&tip_id),
+                     "tip_amount",
+                     TALER_JSON_from_amount (&amount))));
+}
+
+
+/**
+ * Handle a GET "/tips/$ID" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_tips (const struct TMH_RequestHandler *rh,
+                      struct MHD_Connection *connection,
+                      struct TMH_HandlerContext *hc)
+{
+  json_t *pa;
+  enum GNUNET_DB_QueryStatus qs;
+  enum TALER_EXCHANGE_YesNoAll expired;
+  uint64_t offset;
+  int64_t limit;
+
+  if (! (TALER_arg_to_yna (connection,
+                           "expired",
+                           TALER_EXCHANGE_YNA_NO,
+                           &expired)) )
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "expired");
+  {
+    const char *offset_str;
+
+    offset_str = MHD_lookup_connection_value (connection,
+                                              MHD_GET_ARGUMENT_KIND,
+                                              "offset");
+    if (NULL == offset_str)
+    {
+      offset = INT64_MAX;
+    }
+    else
+    {
+      char dummy[2];
+      unsigned long long ull;
+
+      if (1 !=
+          sscanf (offset_str,
+                  "%llu%1s",
+                  &ull,
+                  dummy))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "date");
+      offset = (uint64_t) ull;
+    }
+  }
+  {
+    const char *limit_str;
+
+    limit_str = MHD_lookup_connection_value (connection,
+                                             MHD_GET_ARGUMENT_KIND,
+                                             "limit");
+    if (NULL == limit_str)
+    {
+      limit = -20;
+    }
+    else
+    {
+      char dummy[2];
+      long long ll;
+
+      if (1 !=
+          sscanf (limit_str,
+                  "%lld%1s",
+                  &ll,
+                  dummy))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "limit");
+      limit = (uint64_t) ll;
+    }
+  }
+
+  pa = json_array ();
+  GNUNET_assert (NULL != pa);
+  qs = TMH_db->lookup_tips (TMH_db->cls,
+                            hc->instance->settings.id,
+                            expired,
+                            limit,
+                            offset,
+                            &add_tip,
+                            pa);
+
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    json_decref (pa);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_ORDERS_GET_DB_LOOKUP_ERROR,
+                                       "failed to lookup tips in database");
+  }
+
+  return TALER_MHD_reply_json_pack (connection,
+                                    MHD_HTTP_OK,
+                                    "{s:o}",
+                                    "tips", pa);
+}
diff --git a/src/backend/taler-merchant-httpd_tip-query.h 
b/src/backend/taler-merchant-httpd_private-get-tips.h
similarity index 55%
rename from src/backend/taler-merchant-httpd_tip-query.h
rename to src/backend/taler-merchant-httpd_private-get-tips.h
index 3123486..a892726 100644
--- a/src/backend/taler-merchant-httpd_tip-query.h
+++ b/src/backend/taler-merchant-httpd_private-get-tips.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2017 Taler Systems SA
+  (C) 2020 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,32 +14,28 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_tip-query.h
- * @brief headers for /tip-query handler
- * @author Florian Dold
+ * @file backend/taler-merchant-httpd_private-get-tips.h
+ * @brief headers for GET /private/tips handler
+ * @author Jonathan Buchanan
  */
-#ifndef TALER_MERCHANT_HTTPD_TIP_QUERY_H
-#define TALER_MERCHANT_HTTPD_TIP_QUERY_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TIPS_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
+
 /**
- * Manages a /tip-query call.
+ * Manages a GET /private/tips call.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_tip_query (struct TMH_RequestHandler *rh,
+TMH_private_get_tips (const struct TMH_RequestHandler *rh,
                       struct MHD_Connection *connection,
-                      void **connection_cls,
-                      const char *upload_data,
-                      size_t *upload_data_size,
-                      struct MerchantInstance *mi);
+                      struct TMH_HandlerContext *hc);
+
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-get-transfers.c 
b/src/backend/taler-merchant-httpd_private-get-transfers.c
new file mode 100644
index 0000000..8020fbe
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-transfers.c
@@ -0,0 +1,239 @@
+/*
+  This file is part of TALER
+  (C) 2014-2020 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_private-get-transfers.c
+ * @brief implement API for obtaining a list of wire transfers
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_private-get-transfers.h"
+
+
+/**
+ * Function called with information about a wire transfer.
+ * Generate a response (array entry) based on the given arguments.
+ *
+ * @param cls closure with a `json_t *` array to build up the response
+ * @param credit_amount how much was wired to the merchant (minus fees)
+ * @param wtid wire transfer identifier
+ * @param payto_uri target account that received the wire transfer
+ * @param exchange_url base URL of the exchange that made the wire transfer
+ * @param transfer_serial_id serial number identifying the transfer in the 
backend
+ * @param execution_time when did the exchange make the transfer, 
#GNUNET_TIME_UNIT_FOREVER_ABS
+ *           if it did not yet happen
+ * @param verified YES if we checked the exchange's answer and liked it,
+ *                 NO if we checked the exchange's answer and it is 
problematic,
+ *                 ALL if we did not yet check
+ */
+static void
+transfer_cb (void *cls,
+             const struct TALER_Amount *credit_amount,
+             const struct TALER_WireTransferIdentifierRawP *wtid,
+             const char *payto_uri,
+             const char *exchange_url,
+             uint64_t transfer_serial_id,
+             struct GNUNET_TIME_Absolute execution_time,
+             bool verified,
+             bool confirmed)
+{
+  json_t *ja = cls;
+  json_t *r;
+
+  r = json_pack ("{s:o, s:o, s:s, s:s, s:I}",
+                 "credit_amount", TALER_JSON_from_amount (credit_amount),
+                 "wtid", GNUNET_JSON_from_data_auto (wtid),
+                 "payto_uri", payto_uri,
+                 "exchange_url", exchange_url,
+                 "transfer_serial_id", (json_int_t) transfer_serial_id);
+  GNUNET_assert (NULL != r);
+  if (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+      execution_time.abs_value_us)
+    GNUNET_assert (0 ==
+                   json_object_set_new (
+                     r,
+                     "execution_time",
+                     GNUNET_JSON_from_time_abs (execution_time)));
+  GNUNET_assert (0 ==
+                 json_object_set_new (
+                   r,
+                   "verified",
+                   json_boolean (verified ? 1 : 0)));
+  GNUNET_assert (0 ==
+                 json_object_set_new (
+                   r,
+                   "confirmed",
+                   json_boolean (confirmed ? 1 : 0)));
+  GNUNET_assert (0 ==
+                 json_array_append_new (ja,
+                                        r));
+}
+
+
+/**
+ * Manages a GET /private/transfers call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_get_transfers (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc)
+{
+  const char *payto_uri;
+  struct GNUNET_TIME_Absolute before = GNUNET_TIME_UNIT_FOREVER_ABS;
+  struct GNUNET_TIME_Absolute after = GNUNET_TIME_UNIT_ZERO_ABS;
+  int64_t limit = -20;
+  uint64_t offset;
+  enum TALER_EXCHANGE_YesNoAll verified;
+
+  payto_uri = MHD_lookup_connection_value (connection,
+                                           MHD_GET_ARGUMENT_KIND,
+                                           "payto_uri");
+  {
+    const char *before_s;
+
+    before_s = MHD_lookup_connection_value (connection,
+                                            MHD_GET_ARGUMENT_KIND,
+                                            "before");
+    if ( (NULL != before_s) &&
+         (GNUNET_OK !=
+          GNUNET_STRINGS_fancy_time_to_absolute (before_s,
+                                                 &before)) )
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_PARAMETER_MALFORMED,
+                                         "before");
+  }
+  {
+    const char *after_s;
+
+    after_s = MHD_lookup_connection_value (connection,
+                                           MHD_GET_ARGUMENT_KIND,
+                                           "after");
+    if ( (NULL != after_s) &&
+         (GNUNET_OK !=
+          GNUNET_STRINGS_fancy_time_to_absolute (after_s,
+                                                 &after)) )
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_PARAMETER_MALFORMED,
+                                         "after");
+  }
+  {
+    const char *limit_s;
+
+    limit_s = MHD_lookup_connection_value (connection,
+                                           MHD_GET_ARGUMENT_KIND,
+                                           "limit");
+    if (NULL != limit_s)
+    {
+      char dummy[2];
+      long long l;
+
+      if (1 !=
+          sscanf (limit_s,
+                  "%lld%1s",
+                  &l,
+                  dummy))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "limit");
+      limit = (int64_t) l;
+    }
+  }
+  {
+    const char *offset_s;
+
+    offset_s = MHD_lookup_connection_value (connection,
+                                            MHD_GET_ARGUMENT_KIND,
+                                            "offset");
+    if (NULL != offset_s)
+    {
+      char dummy[2];
+      unsigned long long o;
+
+      if (1 !=
+          sscanf (offset_s,
+                  "%llu%1s",
+                  &o,
+                  dummy))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "offset");
+      offset = (uint64_t) o;
+    }
+    else
+    {
+      if (limit < 0)
+        offset = INT64_MAX;
+      else
+        offset = 0;
+    }
+  }
+  if (! (TALER_arg_to_yna (connection,
+                           "verified",
+                           TALER_EXCHANGE_YNA_ALL,
+                           &verified)) )
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "verified");
+
+  TMH_db->preflight (TMH_db->cls);
+  {
+    json_t *ja;
+    enum GNUNET_DB_QueryStatus qs;
+
+    ja = json_array ();
+    GNUNET_assert (NULL != ja);
+    qs = TMH_db->lookup_transfers (TMH_db->cls,
+                                   hc->instance->settings.id,
+                                   payto_uri,
+                                   before,
+                                   after,
+                                   limit,
+                                   offset,
+                                   verified,
+                                   &transfer_cb,
+                                   ja);
+    if (0 > qs)
+    {
+      /* Simple select queries should not cause serialization issues */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GET_TRANSFERS_DB_FETCH_ERROR,
+                                         "Fail to query database about 
transfers");
+    }
+    return TALER_MHD_reply_json_pack (connection,
+                                      MHD_HTTP_OK,
+                                      "{s:o}",
+                                      "transfers", ja);
+  }
+}
+
+
+/* end of taler-merchant-httpd_track-transfer.c */
diff --git a/src/backend/taler-merchant-httpd_track-transfer.h 
b/src/backend/taler-merchant-httpd_private-get-transfers.h
similarity index 53%
rename from src/backend/taler-merchant-httpd_track-transfer.h
rename to src/backend/taler-merchant-httpd_private-get-transfers.h
index 0463295..556e855 100644
--- a/src/backend/taler-merchant-httpd_track-transfer.h
+++ b/src/backend/taler-merchant-httpd_private-get-transfers.h
@@ -14,36 +14,29 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_track-transfer.h
- * @brief headers for /track/transfer handler
+ * @file backend/taler-merchant-httpd_private-get-transfers.h
+ * @brief headers for GET /transfers handler
  * @author Christian Grothoff
  * @author Marcello Stanisci
  */
-#ifndef TALER_MERCHANT_HTTPD_TRACK_TRANSFER_H
-#define TALER_MERCHANT_HTTPD_TRACK_TRANSFER_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_TRANSFERS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_TRANSFERS_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
+
 /**
- * Manages a /track/transfer call, thus it calls the /wire/transfer
- * offered by the exchange in order to return the set of transfers
- * (of coins) associated with a given wire transfer
+ * Manages a GET /private/transfers call.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_track_transfer (struct TMH_RequestHandler *rh,
+TMH_private_get_transfers (const struct TMH_RequestHandler *rh,
                            struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size,
-                           struct MerchantInstance *mi);
+                           struct TMH_HandlerContext *hc);
 
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-instances-ID.c 
b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
new file mode 100644
index 0000000..b00e959
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
@@ -0,0 +1,375 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-patch-instances.c
+ * @brief implementing PATCH /instances/$ID request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-instances-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Free memory used by @a wm
+ *
+ * @param wm wire method to free
+ */
+static void
+free_wm (struct TMH_WireMethod *wm)
+{
+  json_decref (wm->j_wire);
+  GNUNET_free (wm->wire_method);
+  GNUNET_free (wm);
+}
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
+                                struct MHD_Connection *connection,
+                                struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  struct TALER_MERCHANTDB_InstanceSettings is;
+  json_t *payto_uris;
+  const char *name;
+  struct TMH_WireMethod *wm_head = NULL;
+  struct TMH_WireMethod *wm_tail = NULL;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("payto_uris",
+                           &payto_uris),
+    GNUNET_JSON_spec_string ("name",
+                             &name),
+    GNUNET_JSON_spec_json ("address",
+                           &is.address),
+    GNUNET_JSON_spec_json ("jurisdiction",
+                           &is.jurisdiction),
+    TALER_JSON_spec_amount ("default_max_deposit_fee",
+                            &is.default_max_deposit_fee),
+    TALER_JSON_spec_amount ("default_max_wire_fee",
+                            &is.default_max_wire_fee),
+    GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
+                             &is.default_wire_fee_amortization),
+    GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
+                                    &is.default_wire_transfer_delay),
+    GNUNET_JSON_spec_relative_time ("default_pay_delay",
+                                    &is.default_pay_delay),
+    GNUNET_JSON_spec_end ()
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (NULL != mi);
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+  if (! json_is_array (payto_uris))
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PATCH_INSTANCES_BAD_PAYTO_URIS,
+                                       "Invalid bank account information");
+  for (unsigned int i = 0; i<MAX_RETRIES; i++)
+  {
+    /* Cleanup after earlier loops */
+    {
+      struct TMH_WireMethod *wm;
+
+      while (NULL != (wm = wm_head))
+      {
+        GNUNET_CONTAINER_DLL_remove (wm_head,
+                                     wm_tail,
+                                     wm);
+        free_wm (wm);
+      }
+    }
+    if (GNUNET_OK !=
+        TMH_db->start (TMH_db->cls,
+                       "PATCH /instances"))
+    {
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_PATCH_INSTANCES_DB_START_ERROR,
+                                         "failed to start database 
transaction");
+    }
+    /* Check for equality of settings */
+    if (! ( (0 == strcmp (mi->settings.name,
+                          name)) &&
+            (1 == json_equal (mi->settings.address,
+                              is.address)) &&
+            (1 == json_equal (mi->settings.jurisdiction,
+                              is.jurisdiction)) &&
+            (0 == TALER_amount_cmp_currency (
+               &mi->settings.default_max_deposit_fee,
+               &is.default_max_deposit_fee)) &&
+            (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee,
+                                    &is.default_max_deposit_fee)) &&
+            (0 == TALER_amount_cmp_currency 
(&mi->settings.default_max_wire_fee,
+                                             &is.default_max_wire_fee)) &&
+            (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee,
+                                    &is.default_max_wire_fee)) &&
+            (mi->settings.default_wire_fee_amortization ==
+             is.default_wire_fee_amortization) &&
+            (mi->settings.default_wire_transfer_delay.rel_value_us ==
+             is.default_wire_transfer_delay.rel_value_us) &&
+            (mi->settings.default_pay_delay.rel_value_us ==
+             is.default_pay_delay.rel_value_us) ) )
+    {
+      qs = TMH_db->update_instance (TMH_db->cls,
+                                    &mi->settings);
+      if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+      {
+        TMH_db->rollback (TMH_db->cls);
+        if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+          goto retry;
+        else
+          goto giveup;
+      }
+    }
+
+    /* Check for changes in accounts */
+    {
+      unsigned int len = json_array_size (payto_uris);
+      bool matches[GNUNET_NZL (len)];
+      bool matched;
+
+      memset (matches,
+              0,
+              sizeof (matches));
+      for (struct TMH_WireMethod *wm = mi->wm_head;
+           NULL != wm;
+           wm = wm->next)
+      {
+        const char *uri = json_string_value (json_object_get (wm->j_wire,
+                                                              "payto_uri"));
+        GNUNET_assert (NULL != uri);
+        matched = false;
+        for (unsigned int i = 0; i<len; i++)
+        {
+          const char *str = json_string_value (json_array_get (payto_uris,
+                                                               i));
+          if (NULL == str)
+          {
+            GNUNET_break_op (0);
+            TMH_db->rollback (TMH_db->cls);
+            GNUNET_JSON_parse_free (spec);
+            GNUNET_assert (NULL == wm_head);
+            return TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_BAD_REQUEST,
+                                               
TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS,
+                                               "Invalid bank account 
information");
+          }
+          if ((0 == strcasecmp (uri,
+                                str)) )
+          {
+            if (matches[i])
+            {
+              GNUNET_break (0);
+              TMH_db->rollback (TMH_db->cls);
+              GNUNET_JSON_parse_free (spec);
+              GNUNET_assert (NULL == wm_head);
+              return TALER_MHD_reply_with_error (connection,
+                                                 MHD_HTTP_BAD_REQUEST,
+                                                 
TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS,
+                                                 "Invalid bank account 
information");
+            }
+            matches[i] = true;
+            matched = true;
+            break;
+          }
+        }
+        /* delete unmatched (= removed) accounts */
+        if (! matched)
+        {
+          /* Account was REMOVED */
+          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                      "Existing account `%s' not found, inactivating it.\n",
+                      uri);
+          wm->deleting = true;
+          qs = TMH_db->inactivate_account (TMH_db->cls,
+                                           &wm->h_wire);
+          if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+          {
+            TMH_db->rollback (TMH_db->cls);
+            if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+              goto retry;
+            else
+              goto giveup;
+          }
+        }
+      }
+      /* Find _new_ accounts */
+      for (unsigned int i = 0; i<len; i++)
+      {
+        struct TALER_MERCHANTDB_AccountDetails ad;
+        struct TMH_WireMethod *wm;
+
+        if (matches[i])
+          continue; /* account existed */
+        ad.payto_uri = json_string_value (json_array_get (payto_uris,
+                                                          i));
+        GNUNET_assert (NULL != ad.payto_uri);
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Adding NEW account `%s'\n",
+                    ad.payto_uri);
+        GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                                    &ad.salt,
+                                    sizeof (ad.salt));
+        wm = GNUNET_new (struct TMH_WireMethod);
+        wm->j_wire = json_pack ("{s:s, s:o}",
+                                "payto_uri", ad.payto_uri,
+                                "salt", GNUNET_JSON_from_data_auto (&ad.salt));
+        GNUNET_assert (NULL != wm->j_wire);
+        wm->wire_method
+          = TALER_payto_get_method (ad.payto_uri);
+        GNUNET_assert (NULL != wm->wire_method);
+        /* This also tests for things like the IBAN being malformed */
+        if (GNUNET_OK !=
+            TALER_JSON_merchant_wire_signature_hash (wm->j_wire,
+                                                     &wm->h_wire))
+        {
+          GNUNET_break_op (0);
+          free_wm (wm);
+          while (NULL != (wm = wm_head))
+          {
+            GNUNET_CONTAINER_DLL_remove (wm_head,
+                                         wm_tail,
+                                         wm);
+            free_wm (wm);
+          }
+          TMH_db->rollback (TMH_db->cls);
+          GNUNET_JSON_parse_free (spec);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             
TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS,
+                                             "Invalid bank account 
information");
+        }
+        wm->active = true;
+        GNUNET_CONTAINER_DLL_insert (wm_head,
+                                     wm_tail,
+                                     wm);
+        ad.h_wire = wm->h_wire;
+        ad.active = true;
+        qs = TMH_db->insert_account (TMH_db->cls,
+                                     mi->settings.id,
+                                     &ad);
+        if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+        {
+          TMH_db->rollback (TMH_db->cls);
+          if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+            goto retry;
+          else
+            goto giveup;
+        }
+      }
+    }
+
+    qs = TMH_db->commit (TMH_db->cls);
+retry:
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      continue;
+    break;
+  } /* for(... MAX_RETRIES) */
+giveup:
+  if (0 > qs)
+  {
+    struct TMH_WireMethod *wm;
+
+    while (NULL != (wm = wm_head))
+    {
+      GNUNET_CONTAINER_DLL_remove (wm_head,
+                                   wm_tail,
+                                   wm);
+      free_wm (wm);
+    }
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_PATCH_INSTANCES_DB_COMMIT_ERROR,
+                                       "failed to add instance to database");
+  }
+  /* Deactivate existing wire methods that were removed above */
+  for (struct TMH_WireMethod *wm = mi->wm_head;
+       NULL != wm;
+       wm = wm->next)
+  {
+    /* We did not flip the 'active' bits earlier because the
+       DB transaction could still fail. Now it is time to update our
+       runtime state. */
+    if (wm->deleting)
+      wm->active = false;
+  }
+
+  /* Update our 'settings' */
+  GNUNET_free (mi->settings.name);
+  json_decref (mi->settings.address);
+  json_decref (mi->settings.jurisdiction);
+  is.id = mi->settings.id;
+  mi->settings = is;
+  mi->settings.address = json_incref (mi->settings.address);
+  mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
+  mi->settings.name = GNUNET_strdup (name);
+
+  /* Add 'new' wire methods to our list */
+  {
+    struct TMH_WireMethod *wm;
+
+    while (NULL != (wm = wm_head))
+    {
+      GNUNET_CONTAINER_DLL_remove (wm_head,
+                                   wm_tail,
+                                   wm);
+      GNUNET_CONTAINER_DLL_insert (mi->wm_head,
+                                   mi->wm_tail,
+                                   wm);
+    }
+  }
+
+  GNUNET_JSON_parse_free (spec);
+  return TALER_MHD_reply_static (connection,
+                                 MHD_HTTP_NO_CONTENT,
+                                 NULL,
+                                 NULL,
+                                 0);
+}
+
+
+/* end of taler-merchant-httpd_private-patch-instances-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-instances-ID.h 
b/src/backend/taler-merchant-httpd_private-patch-instances-ID.h
new file mode 100644
index 0000000..dde86a9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.h
@@ -0,0 +1,43 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-patch-instances-ID.h
+ * @brief implementing POST /instances request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
+                                struct MHD_Connection *connection,
+                                struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c 
b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
new file mode 100644
index 0000000..6bbaba2
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
@@ -0,0 +1,237 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-patch-products.c
+ * @brief implementing PATCH /products/$ID request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-products-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Determine the cause of the PATCH failure in more detail and report.
+ *
+ * @param connection connection to report on
+ * @param instance_id instance we are processing
+ * @param product_id ID of the product to patch
+ * @param pd product details we failed to set
+ */
+static MHD_RESULT
+determine_cause (struct MHD_Connection *connection,
+                 const char *instance_id,
+                 const char *product_id,
+                 const struct TALER_MERCHANTDB_ProductDetails *pd)
+{
+  struct TALER_MERCHANTDB_ProductDetails pdx;
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = TMH_db->lookup_product (TMH_db->cls,
+                               instance_id,
+                               product_id,
+                               &pdx);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_PRODUCTS_PATCH_DB_COMMIT_HARD_ERROR,
+                                       "Failed to get existing product");
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                       "Serialization error for 
single-statment request");
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       TALER_EC_PRODUCTS_PATCH_UNKNOWN_PRODUCT,
+                                       "The specified product is unknown");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    break; /* do below */
+  }
+
+  {
+    enum TALER_ErrorCode ec;
+    const char *hint;
+
+    ec = TALER_EC_INTERNAL_INVARIANT_FAILURE;
+    hint = "transaction failed for causes unknown";
+    if (pdx.total_lost > pd->total_lost)
+    {
+      ec = TALER_EC_PRODUCTS_PATCH_TOTAL_LOST_REDUCED;
+      hint = "total lost cannot be lowered";
+    }
+    if (pdx.total_stock > pd->total_stock)
+    {
+      ec = TALER_EC_PRODUCTS_PATCH_TOTAL_STOCKED_REDUCED;
+      hint = "total stocked cannot be lowered";
+    }
+    if (pd->total_stock - pdx.total_sold > pd->total_lost)
+    {
+      ec = TALER_EC_PRODUCTS_PATCH_TOTAL_LOST_EXCEEDS_STOCKS;
+      hint = "total lost cannot exceed total stock minus total sold";
+    }
+    GNUNET_free (pdx.description);
+    json_decref (pdx.description_i18n);
+    GNUNET_free (pdx.unit);
+    json_decref (pdx.taxes);
+    json_decref (pdx.image);
+    json_decref (pdx.address);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       ec,
+                                       hint);
+  }
+}
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
+                               struct MHD_Connection *connection,
+                               struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  const char *product_id = hc->infix;
+  struct TALER_MERCHANTDB_ProductDetails pd;
+  int64_t total_stock;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("description",
+                             (const char **) &pd.description),
+    GNUNET_JSON_spec_json ("description_i18n",
+                           &pd.description_i18n),
+    GNUNET_JSON_spec_string ("unit",
+                             (const char **) &pd.unit),
+    TALER_JSON_spec_amount ("price",
+                            &pd.price),
+    GNUNET_JSON_spec_json ("image",
+                           &pd.image),
+    GNUNET_JSON_spec_json ("taxes",
+                           &pd.taxes),
+    GNUNET_JSON_spec_json ("address",
+                           &pd.address),
+    GNUNET_JSON_spec_int64 ("total_stock",
+                            &total_stock),
+    GNUNET_JSON_spec_uint64 ("total_lost",
+                             &pd.total_lost),
+    GNUNET_JSON_spec_absolute_time ("next_restock",
+                                    &pd.next_restock),
+    GNUNET_JSON_spec_end ()
+  };
+
+  pd.total_sold = 0; /* will be ignored anyway */
+  GNUNET_assert (NULL != mi);
+  GNUNET_assert (NULL != product_id);
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+  if (-1 == total_stock)
+    pd.total_stock = UINT64_MAX;
+  else
+    pd.total_stock = (uint64_t) total_stock;
+  if (NULL != json_object_get (hc->request_body,
+                               "next_restock"))
+  {
+    enum GNUNET_GenericReturnValue res;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_absolute_time ("next_restock",
+                                      &pd.next_restock),
+      GNUNET_JSON_spec_end ()
+    };
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+  else
+  {
+    pd.next_restock.abs_value_us = 0;
+  }
+
+  qs = TMH_db->update_product (TMH_db->cls,
+                               mi->settings.id,
+                               product_id,
+                               &pd);
+  {
+    MHD_RESULT ret;
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      ret = TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        
TALER_EC_PRODUCTS_PATCH_DB_COMMIT_HARD_ERROR,
+                                        "Failed to commit change");
+      break;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      ret = TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                        "Serialization error for 
single-statment request");
+      break;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      ret = determine_cause (connection,
+                             mi->settings.id,
+                             product_id,
+                             &pd);
+      break;
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      ret = TALER_MHD_reply_static (connection,
+                                    MHD_HTTP_NO_CONTENT,
+                                    NULL,
+                                    NULL,
+                                    0);
+      break;
+    }
+    GNUNET_JSON_parse_free (spec);
+    return ret;
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-patch-products-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.h 
b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
new file mode 100644
index 0000000..b034fd0
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
@@ -0,0 +1,43 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-patch-products-ID.h
+ * @brief implementing POST /products request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_PRODUCTS_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_PRODUCTS_ID_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
+                               struct MHD_Connection *connection,
+                               struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-instances.c 
b/src/backend/taler-merchant-httpd_private-post-instances.c
new file mode 100644
index 0000000..350a8ea
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-instances.c
@@ -0,0 +1,423 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-instances.c
+ * @brief implementing POST /instances request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-instances.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Check if the array of @a payto_uris contains exactly the same
+ * URIs as those already in @a mi (possibly in a different order).
+ *
+ * @param mi a merchant instance with accounts
+ * @param payto_uris a JSON array with accounts (presumably)
+ * @return true if they are 'equal', false if not or of payto_uris is not an 
array
+ */
+static bool
+accounts_equal (const struct TMH_MerchantInstance *mi,
+                json_t *payto_uris)
+{
+  if (! json_is_array (payto_uris))
+    return false;
+  {
+    unsigned int len = json_array_size (payto_uris);
+    bool matches[GNUNET_NZL (len)];
+    struct TMH_WireMethod *wm;
+
+    memset (matches,
+            0,
+            sizeof (matches));
+    for (wm = mi->wm_head;
+         NULL != wm;
+         wm = wm->next)
+    {
+      const char *uri = json_string_value (json_object_get (wm->j_wire,
+                                                            "payto_uri"));
+
+      GNUNET_assert (NULL != uri);
+      for (unsigned int i = 0; i<len; i++)
+      {
+        const char *str = json_string_value (json_array_get (payto_uris,
+                                                             i));
+        if (NULL == str)
+          return false;
+        if ( (strcasecmp (uri,
+                          str)) )
+        {
+          if (matches[i])
+          {
+            GNUNET_break (0);
+            return false; /* duplicate entry!? */
+          }
+          matches[i] = true;
+          break;
+        }
+      }
+    }
+    for (unsigned int i = 0; i<len; i++)
+      if (! matches[i])
+        return false;
+  }
+  return true;
+}
+
+
+/**
+ * Free memory used by @a wm
+ *
+ * @param wm wire method to free
+ */
+static void
+free_wm (struct TMH_WireMethod *wm)
+{
+  json_decref (wm->j_wire);
+  GNUNET_free (wm->wire_method);
+  GNUNET_free (wm);
+}
+
+
+/**
+ * Free memory used by @a mi.
+ *
+ * @param mi instance to free
+ */
+static void
+free_mi (struct TMH_MerchantInstance *mi)
+{
+  struct TMH_WireMethod *wm;
+
+  while (NULL != (wm = mi->wm_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (mi->wm_head,
+                                 mi->wm_tail,
+                                 wm);
+    free_wm (wm);
+  }
+  GNUNET_free (mi->settings.id);
+  GNUNET_free (mi->settings.name);
+  GNUNET_free (mi);
+}
+
+
+/**
+ * Generate an instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_instances (const struct TMH_RequestHandler *rh,
+                            struct MHD_Connection *connection,
+                            struct TMH_HandlerContext *hc)
+{
+  struct TALER_MERCHANTDB_InstanceSettings is;
+  json_t *payto_uris;
+  struct TMH_WireMethod *wm_head = NULL;
+  struct TMH_WireMethod *wm_tail = NULL;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("payto_uris",
+                           &payto_uris),
+    GNUNET_JSON_spec_string ("id",
+                             (const char **) &is.id),
+    GNUNET_JSON_spec_string ("name",
+                             (const char **) &is.name),
+    GNUNET_JSON_spec_json ("address",
+                           &is.address),
+    GNUNET_JSON_spec_json ("jurisdiction",
+                           &is.jurisdiction),
+    TALER_JSON_spec_amount ("default_max_deposit_fee",
+                            &is.default_max_deposit_fee),
+    TALER_JSON_spec_amount ("default_max_wire_fee",
+                            &is.default_max_wire_fee),
+    GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
+                             &is.default_wire_fee_amortization),
+    GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
+                                    &is.default_wire_transfer_delay),
+    GNUNET_JSON_spec_relative_time ("default_pay_delay",
+                                    &is.default_pay_delay),
+    GNUNET_JSON_spec_end ()
+  };
+
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+
+  {
+    /* Test if an instance of this id is known */
+    struct TMH_MerchantInstance *mi;
+
+    mi = TMH_lookup_instance (is.id);
+    if (NULL != mi)
+    {
+      /* Check for idempotency */
+      if ( (0 == strcmp (mi->settings.id,
+                         is.id)) &&
+           (0 == strcmp (mi->settings.name,
+                         is.name)) &&
+           (1 == json_equal (mi->settings.address,
+                             is.address)) &&
+           (1 == json_equal (mi->settings.jurisdiction,
+                             is.jurisdiction)) &&
+           (0 == TALER_amount_cmp_currency (
+              &mi->settings.default_max_deposit_fee,
+              &is.default_max_deposit_fee)) &&
+           (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee,
+                                   &is.default_max_deposit_fee)) &&
+           (0 == TALER_amount_cmp_currency (&mi->settings.default_max_wire_fee,
+                                            &is.default_max_wire_fee)) &&
+           (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee,
+                                   &is.default_max_wire_fee)) &&
+           (mi->settings.default_wire_fee_amortization ==
+            is.default_wire_fee_amortization) &&
+           (mi->settings.default_wire_transfer_delay.rel_value_us ==
+            is.default_wire_transfer_delay.rel_value_us) &&
+           (mi->settings.default_pay_delay.rel_value_us ==
+            is.default_pay_delay.rel_value_us) &&
+           (accounts_equal (mi,
+                            payto_uris)) )
+      {
+        GNUNET_JSON_parse_free (spec);
+        return TALER_MHD_reply_static (connection,
+                                       MHD_HTTP_NO_CONTENT,
+                                       NULL,
+                                       NULL,
+                                       0);
+      }
+      else
+      {
+        GNUNET_JSON_parse_free (spec);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_CONFLICT,
+                                           
TALER_EC_POST_INSTANCES_ALREADY_EXISTS,
+                                           "An instance using this identifier 
already exists");
+      }
+    }
+  }
+
+  {
+    bool payto_ok = true;
+    unsigned int len;
+
+    if (! json_is_array (payto_uris))
+    {
+      GNUNET_break_op (0);
+      payto_ok = false;
+      len = 0;
+    }
+    else
+    {
+      len = json_array_size (payto_uris);
+    }
+    for (unsigned int i = 0; i<len; i++)
+    {
+      json_t *payto_uri = json_array_get (payto_uris,
+                                          i);
+
+      if (! json_is_string (payto_uri))
+      {
+        GNUNET_break_op (0);
+        payto_ok = false;
+        break;
+      }
+      /* Test for the same payto:// URI being given twice */
+      for (unsigned int j = 0; j<i; j++)
+      {
+        json_t *old_uri = json_array_get (payto_uris,
+                                          j);
+        if (json_equal (payto_uri,
+                        old_uri))
+        {
+          GNUNET_break_op (0);
+          payto_ok = false;
+          break;
+        }
+      }
+      if (! payto_ok)
+        break;
+
+      {
+        struct TMH_WireMethod *wm;
+        struct GNUNET_HashCode salt;
+
+        GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                                    &salt,
+                                    sizeof (salt));
+        wm = GNUNET_new (struct TMH_WireMethod);
+        wm->j_wire = json_pack ("{s:O, s:o}",
+                                "payto_uri", payto_uri,
+                                "salt", GNUNET_JSON_from_data_auto (&salt));
+        GNUNET_assert (NULL != wm->j_wire);
+        /* This also tests for things like the IBAN being malformed */
+        if (GNUNET_OK !=
+            TALER_JSON_merchant_wire_signature_hash (wm->j_wire,
+                                                     &wm->h_wire))
+        {
+          GNUNET_break_op (0);
+          payto_ok = false;
+          GNUNET_free (wm);
+          break;
+        }
+        wm->wire_method
+          = TALER_payto_get_method (json_string_value (payto_uri));
+        GNUNET_assert (NULL != wm->wire_method);
+        wm->active = true;
+        GNUNET_CONTAINER_DLL_insert (wm_head,
+                                     wm_tail,
+                                     wm);
+      }
+    }
+    if (! payto_ok)
+    {
+      struct TMH_WireMethod *wm;
+
+      while (NULL != (wm = wm_head))
+      {
+        GNUNET_CONTAINER_DLL_remove (wm_head,
+                                     wm_tail,
+                                     wm);
+        free_wm (wm);
+      }
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS,
+                                         "Invalid bank account information");
+    }
+  }
+
+  {
+    struct TMH_MerchantInstance *mi;
+    enum GNUNET_DB_QueryStatus qs;
+
+    mi = GNUNET_new (struct TMH_MerchantInstance);
+    mi->wm_head = wm_head;
+    mi->wm_tail = wm_tail;
+    mi->settings = is;
+    mi->settings.address = json_incref (mi->settings.address);
+    mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
+    mi->settings.id = GNUNET_strdup (is.id);
+    mi->settings.name = GNUNET_strdup (is.name);
+    GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv);
+    GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv,
+                                        &mi->merchant_pub.eddsa_pub);
+
+    for (unsigned int i = 0; i<MAX_RETRIES; i++)
+    {
+      if (GNUNET_OK !=
+          TMH_db->start (TMH_db->cls,
+                         "post /instances"))
+      {
+        GNUNET_JSON_parse_free (spec);
+        free_mi (mi);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_POST_INSTANCES_DB_START_ERROR,
+                                           "failed to start database 
transaction");
+      }
+      qs = TMH_db->insert_instance (TMH_db->cls,
+                                    &mi->merchant_pub,
+                                    &mi->merchant_priv,
+                                    &mi->settings);
+      if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+      {
+        TMH_db->rollback (TMH_db->cls);
+        goto retry;
+      }
+      for (struct TMH_WireMethod *wm = wm_head;
+           NULL != wm;
+           wm = wm->next)
+      {
+        struct TALER_MERCHANTDB_AccountDetails ad;
+        struct GNUNET_JSON_Specification spec[] = {
+          GNUNET_JSON_spec_string ("payto_uri",
+                                   &ad.payto_uri),
+          GNUNET_JSON_spec_fixed_auto ("salt",
+                                       &ad.salt),
+          GNUNET_JSON_spec_end ()
+        };
+
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_MHD_parse_json_data (NULL,
+                                                  wm->j_wire,
+                                                  spec));
+        ad.h_wire = wm->h_wire;
+        ad.active = wm->active;
+        qs = TMH_db->insert_account (TMH_db->cls,
+                                     mi->settings.id,
+                                     &ad);
+        if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+          break;
+      }
+      if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+      {
+        TMH_db->rollback (TMH_db->cls);
+        goto retry;
+      }
+      qs = TMH_db->commit (TMH_db->cls);
+      if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+        qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+retry:
+      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+        break; /* success! -- or hard failure */
+    } /* for .. MAX_RETRIES */
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+    {
+      GNUNET_JSON_parse_free (spec);
+      free_mi (mi);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_POST_INSTANCES_DB_COMMIT_ERROR,
+                                         "failed to add instance to database");
+    }
+    /* Finally, also update our running process */
+    GNUNET_assert (GNUNET_OK ==
+                   TMH_add_instance (mi));
+  }
+  GNUNET_JSON_parse_free (spec);
+  return TALER_MHD_reply_static (connection,
+                                 MHD_HTTP_NO_CONTENT,
+                                 NULL,
+                                 NULL,
+                                 0);
+}
+
+
+/* end of taler-merchant-httpd_private-post-instances.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-instances.h 
b/src/backend/taler-merchant-httpd_private-post-instances.h
new file mode 100644
index 0000000..b30d039
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-instances.h
@@ -0,0 +1,43 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-instances.h
+ * @brief implementing POST /instances request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_INSTANCES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_INSTANCES_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Generate an instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_instances (const struct TMH_RequestHandler *rh,
+                            struct MHD_Connection *connection,
+                            struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c 
b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
new file mode 100644
index 0000000..d585b10
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c
@@ -0,0 +1,273 @@
+/*
+  This file is part of TALER
+  (C) 2014-2020 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_private-post-orders-ID-refund.c
+ * @brief Handle request to increase the refund for an order
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_private-post-orders-ID-refund.h"
+#include "taler-merchant-httpd_private-get-orders.h"
+
+
+/**
+ * How often do we retry the non-trivial refund INSERT database
+ * transaction?
+ */
+#define MAX_RETRIES 5
+
+
+/**
+ * Make a taler://refund URI
+ *
+ * @param connection MHD connection to take host and path from
+ * @param instance_id merchant's instance ID, must not be NULL
+ * @param order_id order ID to show a refund for, must not be NULL
+ * @returns the URI, must be freed with #GNUNET_free
+ */
+static char *
+make_taler_refund_uri (struct MHD_Connection *connection,
+                       const char *instance_id,
+                       const char *order_id)
+{
+  const char *host;
+  const char *forwarded_host;
+  const char *uri_path;
+  const char *uri_instance_id;
+  const char *query;
+  char *result;
+
+  GNUNET_assert (NULL != instance_id);
+  GNUNET_assert (NULL != order_id);
+  host = MHD_lookup_connection_value (connection,
+                                      MHD_HEADER_KIND,
+                                      MHD_HTTP_HEADER_HOST);
+  forwarded_host = MHD_lookup_connection_value (connection,
+                                                MHD_HEADER_KIND,
+                                                "X-Forwarded-Host");
+  if (NULL != forwarded_host)
+    host = forwarded_host;
+  if (NULL == host)
+  {
+    /* Should never happen, at least the host header should be defined */
+    GNUNET_break (0);
+    return NULL;
+  }
+  uri_path = MHD_lookup_connection_value (connection,
+                                          MHD_HEADER_KIND,
+                                          "X-Forwarded-Prefix");
+  if (NULL == uri_path)
+    uri_path = "-";
+  if (0 == strcmp (instance_id,
+                   "default"))
+    uri_instance_id = "-";
+  else
+    uri_instance_id = instance_id;
+  if (GNUNET_YES == TALER_mhd_is_https (connection))
+    query = "";
+  else
+    query = "?insecure=1";
+  GNUNET_assert (0 < GNUNET_asprintf (&result,
+                                      "taler://refund/%s/%s/%s/%s%s",
+                                      host,
+                                      uri_path,
+                                      uri_instance_id,
+                                      order_id,
+                                      query));
+  return result;
+}
+
+
+/**
+ * Handle request for increasing the refund associated with
+ * a contract.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
+                                   struct MHD_Connection *connection,
+                                   struct TMH_HandlerContext *hc)
+{
+  struct TALER_Amount refund;
+  const char *reason;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount ("refund", &refund),
+    GNUNET_JSON_spec_string ("reason", &reason),
+    GNUNET_JSON_spec_end ()
+  };
+  enum TALER_MERCHANTDB_RefundStatus rs;
+
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+
+  TMH_db->preflight (TMH_db->cls);
+  for (unsigned int i = 0; i<MAX_RETRIES; i++)
+  {
+    if (GNUNET_OK !=
+        TMH_db->start (TMH_db->cls,
+                       "increase refund"))
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_REFUND_STORE_DB_ERROR,
+                                         "Could not begin DB transaction");
+    }
+    rs = TMH_db->increase_refund (TMH_db->cls,
+                                  hc->instance->settings.id,
+                                  hc->infix,
+                                  &refund,
+                                  reason);
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "increase refund returned %d\n",
+                rs);
+    if (TALER_MERCHANTDB_RS_SUCCESS != rs)
+      TMH_db->rollback (TMH_db->cls);
+    if (TALER_MERCHANTDB_RS_SOFT_ERROR == rs)
+      continue;
+    if (TALER_MERCHANTDB_RS_SUCCESS == rs)
+    {
+      enum GNUNET_DB_QueryStatus qs;
+
+      qs = TMH_db->commit (TMH_db->cls);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+      {
+        GNUNET_break (0);
+        rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+        break;
+      }
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        continue;
+    }
+    break;
+  } /* retries loop */
+
+  switch (rs)
+  {
+  case TALER_MERCHANTDB_RS_TOO_HIGH:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Refusing refund amount %s that is larger than original 
payment\n",
+                TALER_amount2s (&refund));
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_CONFLICT,
+                                       TALER_EC_REFUND_INCONSISTENT_AMOUNT,
+                                       "Amount above payment");
+  case TALER_MERCHANTDB_RS_HARD_ERROR:
+  case TALER_MERCHANTDB_RS_SOFT_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_REFUND_MERCHANT_DB_COMMIT_ERROR,
+                                       "Internal database error");
+  case TALER_MERCHANTDB_RS_NO_SUCH_ORDER:
+    {
+      enum GNUNET_DB_QueryStatus qs;
+      json_t *contract_terms;
+      uint64_t order_serial;
+
+      qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                          hc->instance->settings.id,
+                                          hc->infix,
+                                          &contract_terms,
+                                          &order_serial);
+      if (qs == 1)
+      {
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_CONFLICT,
+                                           TALER_EC_REFUND_ORDER_ID_UNPAID,
+                                           "Order never paid");
+      }
+      else
+      {
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           TALER_EC_REFUND_ORDER_ID_UNKNOWN,
+                                           "Order unknown");
+      }
+    }
+  case TALER_MERCHANTDB_RS_SUCCESS:
+    break;
+  }
+
+  /* Resume clients that may wait for this refund */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Awakeing clients on %s waiting for refund of less than %s\n",
+              hc->infix,
+              TALER_amount2s (&refund));
+  TMH_long_poll_resume (hc->infix,
+                        hc->instance,
+                        &refund);
+  {
+    struct GNUNET_TIME_Absolute timestamp;
+    uint64_t order_serial;
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = TMH_db->lookup_order_summary (TMH_db->cls,
+                                       hc->instance->settings.id,
+                                       hc->infix,
+                                       &timestamp,
+                                       &order_serial);
+    if (0 >= qs)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_REFUND_DB_INCONSISTENT,
+                                         "Database inconsistent, could not 
trigger notifications");
+    }
+    TMH_notify_order_change (hc->instance,
+                             hc->infix,
+                             true, /* paid */
+                             true, /* refunded */
+                             false, /* wired, cannot be if we could still do 
refunds */
+                             timestamp,
+                             order_serial);
+  }
+  {
+    MHD_RESULT ret;
+    char *taler_refund_uri;
+
+    taler_refund_uri = make_taler_refund_uri (connection,
+                                              hc->instance->settings.id,
+                                              hc->infix);
+    ret = TALER_MHD_reply_json_pack (connection,
+                                     MHD_HTTP_OK,
+                                     "{s:s}",
+                                     "taler_refund_url",
+                                     taler_refund_uri);
+    GNUNET_free (taler_refund_uri);
+    return ret;
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-post-orders-ID-refund.c */
diff --git a/src/backend/taler-merchant-httpd_refund_increase.h 
b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.h
similarity index 52%
rename from src/backend/taler-merchant-httpd_refund_increase.h
rename to src/backend/taler-merchant-httpd_private-post-orders-ID-refund.h
index ff17800..970fe4c 100644
--- a/src/backend/taler-merchant-httpd_refund_increase.h
+++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015, 2016, 2017 Taler Systems SA
+  (C) 2014, 2015, 2016, 2017, 2020 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
@@ -13,15 +13,14 @@
   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_refund_increase.h
- * @brief HTTP serving layer mainly intended to communicate with the frontend
+ * @file backend/taler-merchant-httpd_private-post-orders-ID-refund.h
+ * @brief Handle request to increase the refund for an order
  * @author Marcello Stanisci
+ * @author Christian Grothoff
  */
-
-#ifndef TALER_MERCHANT_HTTPD_REFUND_INCREASE_H
-#define TALER_MERCHANT_HTTPD_REFUND_INCREASE_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_ORDERS_ID_REFUND_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_ORDERS_ID_REFUND_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
@@ -30,20 +29,15 @@
  * Handle request for increasing the refund associated with
  * a contract.
  *
+ * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_refund_increase (struct TMH_RequestHandler *rh,
-                            struct MHD_Connection *connection,
-                            void **connection_cls,
-                            const char *upload_data,
-                            size_t *upload_data_size,
-                            struct MerchantInstance *mi);
+TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
+                                   struct MHD_Connection *connection,
+                                   struct TMH_HandlerContext *hc);
 
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_track-transaction.c 
b/src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c
similarity index 100%
rename from src/backend/taler-merchant-httpd_track-transaction.c
rename to src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.c
diff --git a/src/backend/taler-merchant-httpd_track-transaction.h 
b/src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.h
similarity index 100%
rename from src/backend/taler-merchant-httpd_track-transaction.h
rename to src/backend/taler-merchant-httpd_private-post-orders-ID-track-UNSPEC.h
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c 
b/src/backend/taler-merchant-httpd_private-post-orders.c
new file mode 100644
index 0000000..2772aef
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -0,0 +1,1146 @@
+/*
+  This file is part of TALER
+  (C) 2014, 2015, 2016, 2018, 2020 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_private-post-orders.c
+ * @brief the POST /orders handler
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_private-post-orders.h"
+#include "taler-merchant-httpd_auditors.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_private-get-orders.h"
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * What is the label under which we find/place the merchant's
+ * jurisdiction in the locations list by default?
+ */
+#define STANDARD_LABEL_MERCHANT_JURISDICTION "_mj"
+
+/**
+ * What is the label under which we find/place the merchant's
+ * address in the locations list by default?
+ */
+#define STANDARD_LABEL_MERCHANT_ADDRESS "_ma"
+
+
+/**
+ * Check that the given JSON array of products is well-formed.
+ *
+ * @param products JSON array to check
+ * @return #GNUNET_OK if all is fine
+ */
+static int
+check_products (json_t *products)
+{
+  size_t index;
+  json_t *value;
+
+  if (! json_is_array (products))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  json_array_foreach (products, index, value) {
+    const char *description;
+    const char *error_name;
+    unsigned int error_line;
+    int res;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("description", &description),
+      GNUNET_JSON_spec_end ()
+    };
+
+    /* extract fields we need to sign separately */
+    res = GNUNET_JSON_parse (value,
+                             spec,
+                             &error_name,
+                             &error_line);
+    if (GNUNET_OK != res)
+    {
+      GNUNET_break (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Product parsing failed at #%u: %s:%u\n",
+                  (unsigned int) index,
+                  error_name,
+                  error_line);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_JSON_parse_free (spec);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Generate the base URL for the given merchant instance.
+ *
+ * @param connection the MHD connection
+ * @param instance_id the merchant instance ID
+ * @returns the merchant instance's base URL
+ */
+static char *
+make_merchant_base_url (struct MHD_Connection *connection,
+                        const char *instance_id)
+{
+  const char *host;
+  const char *forwarded_host;
+  const char *uri_path;
+  struct GNUNET_Buffer buf = { 0 };
+
+  if (GNUNET_YES == TALER_mhd_is_https (connection))
+    GNUNET_buffer_write_str (&buf, "https://";);
+  else
+    GNUNET_buffer_write_str (&buf, "http://";);
+  host = MHD_lookup_connection_value (connection,
+                                      MHD_HEADER_KIND,
+                                      "Host");
+  forwarded_host = MHD_lookup_connection_value (connection,
+                                                MHD_HEADER_KIND,
+                                                "X-Forwarded-Host");
+  if (NULL != forwarded_host)
+  {
+    GNUNET_buffer_write_str (&buf,
+                             forwarded_host);
+  }
+  else
+  {
+    GNUNET_assert (NULL != host);
+    GNUNET_buffer_write_str (&buf,
+                             host);
+  }
+  uri_path = MHD_lookup_connection_value (connection,
+                                          MHD_HEADER_KIND,
+                                          "X-Forwarded-Prefix");
+  if (NULL != uri_path)
+  {
+    /* Currently the merchant backend is only supported at the root of the 
path,
+       this might change in the future.  */
+    GNUNET_assert (0);
+  }
+  if (0 != strcmp (instance_id,
+                   "default"))
+  {
+    GNUNET_buffer_write_path (&buf,
+                              "/instances/");
+    GNUNET_buffer_write_str (&buf,
+                             instance_id);
+  }
+  GNUNET_buffer_write_path (&buf,
+                            "");
+  return GNUNET_buffer_reap_str (&buf);
+}
+
+
+/**
+ * Information about a product we are supposed to add to the order
+ * based on what we know it from our inventory.
+ */
+struct InventoryProduct
+{
+  /**
+   * Identifier of the product in the inventory.
+   */
+  const char *product_id;
+
+  /**
+   * Number of units of the product to add to the order.
+   */
+  uint32_t quantity;
+};
+
+
+/**
+ * Execute the database transaction to setup the order.
+ *
+ * @param hc handler context for the request
+ * @param[in] order order to process (not modified)
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products array of products to add to @a order from our 
inventory
+ * @param uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs used to reserve products from @a 
inventory_products
+ * @return transaction status, 0 if @a uuids were insufficient to reserve 
required inventory
+ */
+static enum GNUNET_DB_QueryStatus
+execute_transaction (struct TMH_HandlerContext *hc,
+                     const char *order_id,
+                     struct GNUNET_TIME_Absolute pay_deadline,
+                     json_t *order,
+                     unsigned int inventory_products_length,
+                     const struct InventoryProduct inventory_products[],
+                     unsigned int uuids_length,
+                     const struct GNUNET_Uuid uuids[])
+{
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_TIME_Absolute timestamp;
+  uint64_t order_serial;
+
+  if (GNUNET_OK !=
+      TMH_db->start (TMH_db->cls,
+                     "insert_order"))
+  {
+    GNUNET_break (0);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  /* Setup order */
+  qs = TMH_db->insert_order (TMH_db->cls,
+                             hc->instance->settings.id,
+                             order_id,
+                             pay_deadline,
+                             order);
+  if (qs < 0)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    return qs;
+  }
+  GNUNET_assert (qs > 0);
+  /* Migrate locks from UUIDs to new order: first release old locks */
+  for (unsigned int i = 0; i<uuids_length; i++)
+  {
+    qs = TMH_db->unlock_inventory (TMH_db->cls,
+                                   &uuids[i]);
+    if (qs < 0)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      return qs;
+    }
+    /* qs == 0 is OK here, that just means we did not HAVE any lock under this
+       UUID */
+  }
+  /* Migrate locks from UUIDs to new order: acquire new locks
+     (note: this can basically ONLY fail on serializability OR
+     because the UUID locks were insufficient for the desired
+     quantities). */
+  for (unsigned int i = 0; i<inventory_products_length; i++)
+  {
+    qs = TMH_db->insert_order_lock (TMH_db->cls,
+                                    hc->instance->settings.id,
+                                    order_id,
+                                    inventory_products[i].product_id,
+                                    inventory_products[i].quantity);
+    if (qs <= 0)
+    {
+      /* qs == 0: lock acquisition failed due to insufficient stocks */
+      TMH_db->rollback (TMH_db->cls);
+      return qs;
+    }
+  }
+  /* Get the order serial and timestamp for the order we just created to
+     update long-poll clients. */
+  qs = TMH_db->lookup_order_summary (TMH_db->cls,
+                                     hc->instance->settings.id,
+                                     order_id,
+                                     &timestamp,
+                                     &order_serial);
+  if (1 != qs)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    return qs;
+  }
+  /* finally, commit transaction (note: if it fails, we ALSO re-acquire
+     the UUID locks, which is exactly what we want) */
+  qs = TMH_db->commit (TMH_db->cls);
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    /* Notify clients that have been waiting for the payment to succeed */
+    TMH_long_poll_resume (order_id,
+                          hc->instance,
+                          NULL);
+    TMH_notify_order_change (hc->instance,
+                             order_id,
+                             false, /* paid */
+                             false, /* refunded */
+                             false, /* wired */
+                             timestamp,
+                             order_serial);
+
+    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* 1 == success! */
+  }
+  return qs;
+}
+
+
+/**
+ * Transform an order into a proposal and store it in the
+ * database. Write the resulting proposal or an error message
+ * of a MHD connection.
+ *
+ * @param connection connection to write the result or error to
+ * @param hc handler context for the request
+ * @param[in,out] order order to process (can be modified)
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products array of products to add to @a order from our 
inventory
+ * @param uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs used to reserve products from @a 
inventory_products
+ * @return MHD result code
+ */
+static MHD_RESULT
+execute_order (struct MHD_Connection *connection,
+               struct TMH_HandlerContext *hc,
+               json_t *order,
+               unsigned int inventory_products_length,
+               const struct InventoryProduct inventory_products[],
+               unsigned int uuids_length,
+               const struct GNUNET_Uuid uuids[])
+{
+  const struct TALER_MERCHANTDB_InstanceSettings *settings =
+    &hc->instance->settings;
+  struct TALER_Amount total;
+  const char *order_id;
+  const char *summary;
+  const char *fulfillment_url;
+  json_t *products;
+  json_t *merchant;
+  struct GNUNET_TIME_Absolute timestamp;
+  struct GNUNET_TIME_Absolute refund_deadline;
+  struct GNUNET_TIME_Absolute wire_transfer_deadline;
+  struct GNUNET_TIME_Absolute pay_deadline;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount ("amount",
+                            &total),
+    GNUNET_JSON_spec_string ("order_id",
+                             &order_id),
+    GNUNET_JSON_spec_string ("summary",
+                             &summary),
+    GNUNET_JSON_spec_string ("fulfillment_url",
+                             &fulfillment_url),
+    /**
+     * 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),
+    GNUNET_JSON_spec_absolute_time ("refund_deadline",
+                                    &refund_deadline),
+    GNUNET_JSON_spec_absolute_time ("pay_deadline",
+                                    &pay_deadline),
+    GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
+                                    &wire_transfer_deadline),
+    GNUNET_JSON_spec_end ()
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  /* extract fields we need to sign separately */
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     order,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+  if (0 !=
+      strcasecmp (total.currency,
+                  TMH_currency))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_BAD_REQUEST,
+      TALER_EC_PROPOSAL_ORDER_BAD_CURRENCY,
+      "Total amount must be in currency supported by backend");
+  }
+
+  if (wire_transfer_deadline.abs_value_us <
+      refund_deadline.abs_value_us)
+  {
+    GNUNET_JSON_parse_free (spec);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "invariant failed: wire_transfer_deadline >= 
refund_deadline\n");
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "wire_transfer_deadline: %s\n",
+                GNUNET_STRINGS_absolute_time_to_string (
+                  wire_transfer_deadline));
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "refund_deadline: %s\n",
+                GNUNET_STRINGS_absolute_time_to_string (refund_deadline));
+    return TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_BAD_REQUEST,
+      TALER_EC_PARAMETER_MALFORMED,
+      "order:wire_transfer_deadline;order:refund_deadline");
+  }
+
+
+  /* check contract is well-formed */
+  if (GNUNET_OK != check_products (products))
+  {
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MALFORMED,
+                                       "order:products");
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Executing database transaction to create order '%s' for 
instance '%s'\n",
+              order_id,
+              settings->id);
+  for (unsigned int i = 0; i<MAX_RETRIES; i++)
+  {
+    TMH_db->preflight (TMH_db->cls);
+    qs = execute_transaction (hc,
+                              order_id,
+                              pay_deadline,
+                              order,
+                              inventory_products_length,
+                              inventory_products,
+                              uuids_length,
+                              uuids);
+    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+      break;
+  }
+  if (0 > qs)
+  {
+    /* Special report if retries insufficient */
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_PROPOSAL_STORE_DB_ERROR_SOFT,
+                                         "serialization error, maybe try 
again?");
+    }
+
+    {
+      /* Hard error could be constraint violation,
+         check if order already exists */
+      TMH_db->preflight (TMH_db->cls);
+      qs = TMH_db->lookup_order (TMH_db->cls,
+                                 settings->id,
+                                 order_id,
+                                 NULL);
+      if (0 < qs)
+      {
+        /* Yep, indeed uniqueness constraint violation */
+        int rv;
+        char *msg;
+
+        GNUNET_JSON_parse_free (spec);
+        GNUNET_asprintf (&msg,
+                         "order ID `%s' already exists",
+                         order_id);
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Order `%s' already exists\n",
+                    order_id);
+        /* contract_terms may be private, only expose
+         * duplicate order_id to the network */
+        rv = TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST, /* or conflict? 
*/
+                                         
TALER_EC_PROPOSAL_STORE_DB_ERROR_ALREADY_EXISTS,
+                                         msg);
+        GNUNET_free (msg);
+        return rv;
+      }
+    }
+
+    /* Other hard transaction error (disk full, etc.) */
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_INTERNAL_SERVER_ERROR,
+      TALER_EC_PROPOSAL_STORE_DB_ERROR_HARD,
+      "Failed to store the order in the DB");
+  }
+  /* DB transaction succeeded, generate positive response */
+  {
+    MHD_RESULT ret;
+
+    ret = TALER_MHD_reply_json_pack (connection,
+                                     MHD_HTTP_OK,
+                                     "{s:s}",
+                                     "order_id",
+                                     order_id);
+    GNUNET_JSON_parse_free (spec);
+    return ret;
+  }
+}
+
+
+/**
+ * Add missing fields to the order.  Upon success, continue
+ * processing with execute_order().
+ *
+ * @param connection connection to write the result or error to
+ * @param hc handler context for the request
+ * @param[in,out] order order to process (can be modified)
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products array of products to add to @a order from our 
inventory
+ * @param uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs used to reserve products from @a 
inventory_products
+ * @return MHD result code
+ */
+static MHD_RESULT
+patch_order (struct MHD_Connection *connection,
+             struct TMH_HandlerContext *hc,
+             json_t *order,
+             unsigned int inventory_products_length,
+             const struct InventoryProduct inventory_products[],
+             unsigned int uuids_length,
+             const struct GNUNET_Uuid uuids[])
+{
+  const struct TALER_MERCHANTDB_InstanceSettings *settings =
+    &hc->instance->settings;
+
+  /* Add order_id if it doesn't exist. */
+  if (NULL ==
+      json_string_value (json_object_get (order,
+                                          "order_id")))
+  {
+    char buf[256];
+    time_t timer;
+    struct tm*tm_info;
+    size_t off;
+    uint64_t rand;
+    char *last;
+
+    time (&timer);
+    tm_info = localtime (&timer);
+    if (NULL == tm_info)
+    {
+      return TALER_MHD_reply_with_error
+               (connection,
+               MHD_HTTP_INTERNAL_SERVER_ERROR,
+               TALER_EC_PROPOSAL_NO_LOCALTIME,
+               "failed to determine local time");
+    }
+    off = strftime (buf,
+                    sizeof (buf),
+                    "%Y.%j",
+                    tm_info);
+    buf[off++] = '-';
+    rand = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
+                                     UINT64_MAX);
+    last = GNUNET_STRINGS_data_to_string (&rand,
+                                          sizeof (uint64_t),
+                                          &buf[off],
+                                          sizeof (buf) - off);
+    *last = '\0';
+    json_object_set_new (order,
+                         "order_id",
+                         json_string (buf));
+  }
+
+  /* Add timestamp if it doesn't exist */
+  if (NULL == json_object_get (order,
+                               "timestamp"))
+  {
+    struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+
+    (void) GNUNET_TIME_round_abs (&now);
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "timestamp",
+                                        GNUNET_JSON_from_time_abs (now)));
+  }
+
+  /* If no refund_deadline given, set one as zero.  */
+  if (NULL == json_object_get (order,
+                               "refund_deadline"))
+  {
+    const char *refdel_s;
+    struct GNUNET_TIME_Absolute rd = { 0 };
+
+    refdel_s = MHD_lookup_connection_value (connection,
+                                            MHD_GET_ARGUMENT_KIND,
+                                            "refund_delay");
+    if (NULL == refdel_s)
+    {
+      struct GNUNET_TIME_Relative r;
+
+      if (GNUNET_OK !=
+          GNUNET_STRINGS_fancy_time_to_relative (refdel_s,
+                                                 &r))
+      {
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "refund_delay");
+
+      }
+      rd = GNUNET_TIME_relative_to_absolute (r);
+    }
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "refund_deadline",
+                                        GNUNET_JSON_from_time_abs (rd)));
+  }
+
+  if (NULL == json_object_get (order,
+                               "pay_deadline"))
+  {
+    struct GNUNET_TIME_Absolute t;
+
+    t = GNUNET_TIME_relative_to_absolute (settings->default_pay_delay);
+    (void) GNUNET_TIME_round_abs (&t);
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "pay_deadline",
+                                        GNUNET_JSON_from_time_abs (t)));
+  }
+
+  if (NULL == json_object_get (order,
+                               "wire_transfer_deadline"))
+  {
+    struct GNUNET_TIME_Absolute t;
+    t = GNUNET_TIME_relative_to_absolute (
+      settings->default_wire_transfer_delay);
+    (void) GNUNET_TIME_round_abs (&t);
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "wire_transfer_deadline",
+                                        GNUNET_JSON_from_time_abs (t)));
+  }
+
+  if (NULL == json_object_get (order,
+                               "max_wire_fee"))
+  {
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "max_wire_fee",
+                                        TALER_JSON_from_amount
+                                          (&settings->default_max_wire_fee)));
+  }
+
+  if (NULL == json_object_get (order,
+                               "max_fee"))
+  {
+    GNUNET_assert (0 ==
+                   json_object_set_new (
+                     order,
+                     "max_fee",
+                     TALER_JSON_from_amount
+                       (&settings->default_max_deposit_fee)));
+  }
+
+  if (NULL == json_object_get (order,
+                               "wire_fee_amortization"))
+  {
+    GNUNET_assert (0 ==
+                   json_object_set_new (
+                     order,
+                     "wire_fee_amortization",
+                     json_integer
+                       ((json_int_t) 
settings->default_wire_fee_amortization)));
+  }
+
+  if (NULL == json_object_get (order,
+                               "merchant_base_url"))
+  {
+    char *url;
+
+    url = make_merchant_base_url (connection,
+                                  settings->id);
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "merchant_base_url",
+                                        json_string (url)));
+    GNUNET_free (url);
+  }
+
+
+  /* Fill in merchant information if necessary */
+  if (NULL == json_object_get (order,
+                               "merchant"))
+  {
+    const char *mj = NULL;
+    const char *ma = NULL;
+    json_t *locations;
+    json_t *jmerchant;
+
+    jmerchant = json_object ();
+    GNUNET_assert (NULL != jmerchant);
+    GNUNET_assert (0 ==
+                   json_object_set_new (jmerchant,
+                                        "name",
+                                        json_string (settings->name)));
+    GNUNET_assert (0 ==
+                   json_object_set_new (jmerchant,
+                                        "instance",
+                                        json_string (settings->id)));
+    locations = json_object_get (order,
+                                 "locations");
+    if (NULL != locations)
+    {
+      json_t *loca;
+      json_t *locj;
+
+      /* Handle merchant address */
+      loca = settings->address;
+      if (NULL != loca)
+      {
+        loca = json_deep_copy (loca);
+        ma = STANDARD_LABEL_MERCHANT_ADDRESS;
+        GNUNET_assert (0 ==
+                       json_object_set_new (locations,
+                                            ma,
+                                            loca));
+        GNUNET_assert (0 ==
+                       json_object_set_new (jmerchant,
+                                            "address",
+                                            json_string (ma)));
+      }
+
+      /* Handle merchant jurisdiction */
+      locj = settings->jurisdiction;
+      if (NULL != locj)
+      {
+        if ( (NULL != loca) &&
+             (1 == json_equal (locj,
+                               loca)) )
+        {
+          /* addresses equal, re-use */
+          mj = ma;
+        }
+        else
+        {
+          locj = json_deep_copy (locj);
+          mj = STANDARD_LABEL_MERCHANT_JURISDICTION;
+          GNUNET_assert (0 ==
+                         json_object_set_new (locations,
+                                              mj,
+                                              locj));
+        }
+        GNUNET_assert (0 ==
+                       json_object_set_new (jmerchant,
+                                            "jurisdiction",
+                                            json_string (mj)));
+      }
+    } /* have locations */
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "merchant",
+                                        jmerchant));
+  } /* needed to synthesize merchant info */
+
+  /* add fields to the contract that the backend should provide */
+  GNUNET_assert (0 ==
+                 json_object_set (order,
+                                  "exchanges",
+                                  TMH_trusted_exchanges));
+  GNUNET_assert (0 ==
+                 json_object_set (order,
+                                  "auditors",
+                                  j_auditors));
+  GNUNET_assert (0 ==
+                 json_object_set_new (order,
+                                      "merchant_pub",
+                                      GNUNET_JSON_from_data_auto (
+                                        &hc->instance->merchant_pub)));
+  return execute_order (connection,
+                        hc,
+                        order,
+                        inventory_products_length,
+                        inventory_products,
+                        uuids_length,
+                        uuids);
+}
+
+
+/**
+ * Process the @a payment_target and add the details of how the
+ * order could be paid to @a order. On success, continue
+ * processing with patch_order().
+ *
+ * @param connection connection to write the result or error to
+ * @param hc handler context for the request
+ * @param[in,out] order order to process (can be modified)
+ * @param payment_target desired wire method, NULL for no preference
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products array of products to add to @a order from our 
inventory
+ * @param uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs used to reserve products from @a 
inventory_products
+ * @return MHD result code
+ */
+static MHD_RESULT
+add_payment_details (struct MHD_Connection *connection,
+                     struct TMH_HandlerContext *hc,
+                     json_t *order,
+                     const char *payment_target,
+                     unsigned int inventory_products_length,
+                     const struct InventoryProduct inventory_products[],
+                     unsigned int uuids_length,
+                     const struct GNUNET_Uuid uuids[])
+{
+  struct TMH_WireMethod *wm;
+
+  wm = hc->instance->wm_head;
+  if (NULL != payment_target)
+  {
+    while ( (NULL != wm) &&
+            (GNUNET_YES == wm->active) &&
+            (0 != strcasecmp (payment_target,
+                              wm->wire_method) ) )
+      wm = wm->next;
+  }
+  if (GNUNET_YES != wm->active)
+    wm = NULL;
+  if (NULL == wm)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "No wire method available for instance '%s'\n",
+                hc->instance->settings.id);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       
TALER_EC_PROPOSAL_INSTANCE_CONFIGURATION_LACKS_WIRE,
+                                       "No wire method configured for 
instance");
+  }
+  GNUNET_assert (0 ==
+                 json_object_set_new (order,
+                                      "h_wire",
+                                      GNUNET_JSON_from_data_auto (
+                                        &wm->h_wire)));
+  GNUNET_assert (0 ==
+                 json_object_set_new (order,
+                                      "wire_method",
+                                      json_string (wm->wire_method)));
+  return patch_order (connection,
+                      hc,
+                      order,
+                      inventory_products_length,
+                      inventory_products,
+                      uuids_length,
+                      uuids);
+}
+
+
+/**
+ * Merge the inventory products into @a order, querying the
+ * database about the details of those products. Upon success,
+ * continue processing by calling add_payment_details().
+ *
+ * @param connection connection to write the result or error to
+ * @param hc handler context for the request
+ * @param[in,out] order order to process (can be modified)
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products array of products to add to @a order from our 
inventory
+ * @param uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs used to reserve products from @a 
inventory_products
+ * @return MHD result code
+ */
+static MHD_RESULT
+merge_inventory (struct MHD_Connection *connection,
+                 struct TMH_HandlerContext *hc,
+                 json_t *order,
+                 const char *payment_target,
+                 unsigned int inventory_products_length,
+                 const struct InventoryProduct inventory_products[],
+                 unsigned int uuids_length,
+                 const struct GNUNET_Uuid uuids[])
+{
+  if (NULL == json_object_get (order,
+                               "products"))
+  {
+    GNUNET_assert (0 ==
+                   json_object_set_new (order,
+                                        "products",
+                                        json_array ()));
+  }
+
+  {
+    json_t *np = json_array ();
+
+    for (unsigned int i = 0; i<inventory_products_length; i++)
+    {
+      struct TALER_MERCHANTDB_ProductDetails pd;
+      enum GNUNET_DB_QueryStatus qs;
+
+      qs = TMH_db->lookup_product (TMH_db->cls,
+                                   hc->instance->settings.id,
+                                   inventory_products[i].product_id,
+                                   &pd);
+      if (qs <= 0)
+      {
+        enum TALER_ErrorCode ec;
+        unsigned int http_status;
+
+        switch (qs)
+        {
+        case GNUNET_DB_STATUS_HARD_ERROR:
+          http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+          ec = TALER_EC_ORDERS_LOOKUP_PRODUCT_DB_HARD_FAILURE;
+          break;
+        case GNUNET_DB_STATUS_SOFT_ERROR:
+          GNUNET_break (0);
+          http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+          ec = TALER_EC_ORDERS_LOOKUP_PRODUCT_DB_SOFT_FAILURE;
+          break;
+        case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+          http_status = MHD_HTTP_NOT_FOUND;
+          ec = TALER_EC_ORDERS_LOOKUP_PRODUCT_NOT_FOUND;
+          break;
+        case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+          /* case listed to make compilers happy */
+          GNUNET_assert (0);
+        }
+        json_decref (np);
+        return TALER_MHD_reply_with_error (connection,
+                                           http_status,
+                                           ec,
+                                           inventory_products[i].product_id);
+      }
+      {
+        json_t *p;
+
+        p = json_pack ("{s:s, s:o, s:s, s:o, s:o, s:o}",
+                       "description",
+                       pd.description,
+                       "description_i18n",
+                       pd.description_i18n,
+                       "unit",
+                       pd.unit,
+                       "price",
+                       TALER_JSON_from_amount (&pd.price),
+                       "taxes",
+                       pd.taxes,
+                       "image",
+                       pd.image);
+        GNUNET_assert (NULL != p);
+        GNUNET_assert (0 ==
+                       json_array_append_new (np,
+                                              p));
+      }
+      GNUNET_free (pd.description);
+      GNUNET_free (pd.unit);
+      json_decref (pd.address);
+    }
+    /* merge into existing products list */
+    {
+      json_t *xp;
+
+      xp = json_object_get (order,
+                            "products");
+      GNUNET_assert (NULL != xp);
+      json_array_extend (xp, np);
+      json_decref (np);
+    }
+  }
+  return add_payment_details (connection,
+                              hc,
+                              order,
+                              payment_target,
+                              inventory_products_length,
+                              inventory_products,
+                              uuids_length,
+                              uuids);
+}
+
+
+/**
+ * Generate an order.  We add the fields 'exchanges', 'merchant_pub', and
+ * 'H_wire' to the order gotten from the frontend, as well as possibly other
+ * fields if the frontend did not provide them. Returns the order_id.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_orders (const struct TMH_RequestHandler *rh,
+                         struct MHD_Connection *connection,
+                         struct TMH_HandlerContext *hc)
+{
+  json_t *order;
+  const char *payment_target = NULL;
+  unsigned int ips_len = 0;
+  struct InventoryProduct *ips = NULL;
+  unsigned int uuids_len = 0;
+  struct GNUNET_Uuid *uuids = NULL;
+
+  (void) rh;
+  order = json_object_get (hc->request_body,
+                           "order");
+  if (NULL == order)
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_PARAMETER_MISSING,
+                                       "order");
+  }
+
+  /* parse the payment_target (optionally given) */
+  {
+    const json_t *pt;
+
+    pt = json_object_get (hc->request_body,
+                          "payment_target");
+    if (NULL != pt)
+    {
+      if (! json_is_string (pt))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "payment_target");
+      payment_target = json_string_value (pt);
+    }
+  }
+  /* parse the inventory_products (optionally given) */
+  {
+    const json_t *ip;
+
+    ip = json_object_get (hc->request_body,
+                          "inventory_products");
+    if (NULL != ip)
+    {
+      if (! json_is_array (ip))
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "inventory_products");
+      GNUNET_array_grow (ips,
+                         ips_len,
+                         json_array_size (ip));
+      for (unsigned int i = 0; i<ips_len; i++)
+      {
+        const char *error_name;
+        unsigned int error_line;
+        int res;
+        struct GNUNET_JSON_Specification spec[] = {
+          GNUNET_JSON_spec_string ("product_id",
+                                   &ips[i].product_id),
+          GNUNET_JSON_spec_uint32 ("quantity",
+                                   &ips[i].quantity),
+          GNUNET_JSON_spec_end ()
+        };
+
+        res = GNUNET_JSON_parse (json_array_get (ip,
+                                                 i),
+                                 spec,
+                                 &error_name,
+                                 &error_line);
+        if (GNUNET_OK != res)
+        {
+          GNUNET_break_op (0);
+          GNUNET_array_grow (ips,
+                             ips_len,
+                             0);
+          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                      "Product parsing failed at #%u: %s:%u\n",
+                      i,
+                      error_name,
+                      error_line);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             TALER_EC_PARAMETER_MALFORMED,
+                                             "inventory_products");
+        }
+      }
+    }
+  }
+  /* parse the lock_uuids (optionally given) */
+  {
+    const json_t *uuid;
+
+    uuid = json_object_get (hc->request_body,
+                            "lock_uuids");
+    if (NULL != uuid)
+    {
+      if (! json_is_array (uuid))
+      {
+        GNUNET_array_grow (ips,
+                           ips_len,
+                           0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_PARAMETER_MALFORMED,
+                                           "lock_uuids");
+      }
+      GNUNET_array_grow (uuids,
+                         uuids_len,
+                         json_array_size (uuid));
+      for (unsigned int i = 0; i<uuids_len; i++)
+      {
+        const char *error_name;
+        unsigned int error_line;
+        int res;
+        struct GNUNET_JSON_Specification spec[] = {
+          GNUNET_JSON_spec_fixed_auto ("uuid",
+                                       &uuids[i]),
+          GNUNET_JSON_spec_end ()
+        };
+
+        res = GNUNET_JSON_parse (json_array_get (uuid,
+                                                 i),
+                                 spec,
+                                 &error_name,
+                                 &error_line);
+        if (GNUNET_OK != res)
+        {
+          GNUNET_break_op (0);
+          GNUNET_array_grow (ips,
+                             ips_len,
+                             0);
+          GNUNET_array_grow (uuids,
+                             uuids_len,
+                             0);
+          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                      "UUID parsing failed at #%u: %s:%u\n",
+                      i,
+                      error_name,
+                      error_line);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             TALER_EC_PARAMETER_MALFORMED,
+                                             "lock_uuids");
+        }
+      }
+    }
+  }
+  /* Finally, start by completing the order */
+  {
+    MHD_RESULT res;
+
+    res = merge_inventory (connection,
+                           hc,
+                           order,
+                           payment_target,
+                           ips_len,
+                           ips,
+                           uuids_len,
+                           uuids);
+    GNUNET_array_grow (ips,
+                       ips_len,
+                       0);
+    GNUNET_array_grow (uuids,
+                       uuids_len,
+                       0);
+    return res;
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-post-orders.c */
diff --git a/src/backend/taler-merchant-httpd_config.h 
b/src/backend/taler-merchant-httpd_private-post-orders.h
similarity index 51%
copy from src/backend/taler-merchant-httpd_config.h
copy to src/backend/taler-merchant-httpd_private-post-orders.h
index 1a5dfd6..ff73986 100644
--- a/src/backend/taler-merchant-httpd_config.h
+++ b/src/backend/taler-merchant-httpd_private-post-orders.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2019 Taler Systems SA
+  (C) 2014, 2015, 2019 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,32 +14,29 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_config.h
- * @brief headers for /config handler
- * @author Florian Dold
+ * @file backend/taler-merchant-httpd_private-post-orders.h
+ * @brief headers for POST /orders handler
+ * @author Marcello Stanisci
  */
-#ifndef TALER_MERCHANT_HTTPD_CONFIG_H
-#define TALER_MERCHANT_HTTPD_CONFIG_H
-#include <microhttpd.h>
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_ORDERS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_ORDERS_H
+
 #include "taler-merchant-httpd.h"
 
 /**
- * Manages a /config call.
+ * Generate an order.  We add the fields 'exchanges', 'merchant_pub', and
+ * 'H_wire' to the order gotten from the frontend, as well as possibly other
+ * fields if the frontend did not provide them. Returns the order_id.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_config (struct TMH_RequestHandler *rh,
-                   struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size,
-                   struct MerchantInstance *mi);
+TMH_private_post_orders (const struct TMH_RequestHandler *rh,
+                         struct MHD_Connection *connection,
+                         struct TMH_HandlerContext *hc);
+
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_private-post-products-ID-lock.c 
b/src/backend/taler-merchant-httpd_private-post-products-ID-lock.c
new file mode 100644
index 0000000..ddd891a
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-products-ID-lock.c
@@ -0,0 +1,118 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-products-ID-lock.c
+ * @brief implementing POST /products/$ID/lock request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-products-ID-lock.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Lock an existing product.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_products_ID_lock (const struct TMH_RequestHandler *rh,
+                                   struct MHD_Connection *connection,
+                                   struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  const char *product_id = hc->infix;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_Uuid uuid;
+  uint32_t quantity;
+  struct GNUNET_TIME_Relative duration;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("lock_uuid",
+                                 &uuid),
+    GNUNET_JSON_spec_uint32 ("quantity",
+                             &quantity),
+    GNUNET_JSON_spec_relative_time ("duration",
+                                    &duration),
+    GNUNET_JSON_spec_end ()
+  };
+
+  GNUNET_assert (NULL != mi);
+  GNUNET_assert (NULL != product_id);
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+
+  qs = TMH_db->lock_product (TMH_db->cls,
+                             mi->settings.id,
+                             product_id,
+                             &uuid,
+                             quantity,
+                             GNUNET_TIME_relative_to_absolute (duration));
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_PRODUCTS_PATCH_DB_COMMIT_HARD_ERROR,
+                                       "Failed to execute DB transaction to 
lock product");
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                       "Serialization error for 
single-statment request");
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    qs = TMH_db->lookup_product (TMH_db->cls,
+                                 mi->settings.id,
+                                 product_id,
+                                 NULL);
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_PRODUCTS_LOCK_UNKNOWN_PRODUCT,
+                                         "The specified product is unknown");
+    else
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_CONFLICT,
+                                         
TALER_EC_PRODUCTS_LOCK_INSUFFICIENT_STOCKS,
+                                         "The specified product is out of 
stock");
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    return TALER_MHD_reply_static (connection,
+                                   MHD_HTTP_NO_CONTENT,
+                                   NULL,
+                                   NULL,
+                                   0);
+  }
+  GNUNET_assert (0);
+  return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-patch-products-ID-lock.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-products-ID-lock.h 
b/src/backend/taler-merchant-httpd_private-post-products-ID-lock.h
new file mode 100644
index 0000000..0d5d9a3
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-products-ID-lock.h
@@ -0,0 +1,43 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-products-ID-lock.h
+ * @brief implementing POST /products/$ID/lock request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_ID_LOCK_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_ID_LOCK_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Lock an existing product.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_products_ID_lock (const struct TMH_RequestHandler *rh,
+                                   struct MHD_Connection *connection,
+                                   struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-products.c 
b/src/backend/taler-merchant-httpd_private-post-products.c
new file mode 100644
index 0000000..c291845
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-products.c
@@ -0,0 +1,242 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-products.c
+ * @brief implementing POST /products request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-products.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Check if the two products are identical.
+ *
+ * @param p1 product to compare
+ * @param p2 other product to compare
+ * @return true if they are 'equal', false if not or of payto_uris is not an 
array
+ */
+static bool
+products_equal (const struct TALER_MERCHANTDB_ProductDetails *p1,
+                const struct TALER_MERCHANTDB_ProductDetails *p2)
+{
+  return ( (0 == strcmp (p1->description,
+                         p2->description)) &&
+           (1 == json_equal (p1->description_i18n,
+                             p2->description_i18n)) &&
+           (0 == strcmp (p1->unit,
+                         p2->unit)) &&
+           (0 == TALER_amount_cmp_currency (&p1->price,
+                                            &p2->price)) &&
+           (0 == TALER_amount_cmp (&p1->price,
+                                   &p2->price)) &&
+           (1 == json_equal (p1->taxes,
+                             p2->taxes)) &&
+           (p1->total_stock == p2->total_stock) &&
+           (p1->total_sold == p2->total_sold) &&
+           (p1->total_lost == p2->total_lost) &&
+           (1 == json_equal (p1->image,
+                             p2->image)) &&
+           (1 == json_equal (p1->address,
+                             p2->address)) &&
+           (p1->next_restock.abs_value_us ==
+            p2->next_restock.abs_value_us) );
+}
+
+
+/**
+ * Generate an instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_products (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc)
+{
+  struct TMH_MerchantInstance *mi = hc->instance;
+  struct TALER_MERCHANTDB_ProductDetails pd;
+  const char *product_id;
+  int64_t total_stock;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("product_id",
+                             &product_id),
+    GNUNET_JSON_spec_string ("description",
+                             (const char **) &pd.description),
+    GNUNET_JSON_spec_json ("description_i18n",
+                           &pd.description_i18n),
+    GNUNET_JSON_spec_string ("unit",
+                             (const char **) &pd.unit),
+    TALER_JSON_spec_amount ("price",
+                            &pd.price),
+    GNUNET_JSON_spec_json ("image",
+                           &pd.image),
+    GNUNET_JSON_spec_json ("taxes",
+                           &pd.taxes),
+    GNUNET_JSON_spec_json ("address",
+                           &pd.address),
+    GNUNET_JSON_spec_int64 ("total_stock",
+                            &total_stock),
+    GNUNET_JSON_spec_absolute_time ("next_restock",
+                                    &pd.next_restock),
+    GNUNET_JSON_spec_end ()
+  };
+
+  GNUNET_assert (NULL != mi);
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+
+  if (-1 == total_stock)
+    pd.total_stock = UINT64_MAX;
+  else
+    pd.total_stock = (uint64_t) total_stock;
+  if (NULL != json_object_get (hc->request_body,
+                               "next_restock"))
+  {
+    enum GNUNET_GenericReturnValue res;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_absolute_time ("next_restock",
+                                      &pd.next_restock),
+      GNUNET_JSON_spec_end ()
+    };
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+  }
+  else
+  {
+    pd.next_restock.abs_value_us = 0;
+  }
+
+  for (unsigned int i = 0; i<MAX_RETRIES; i++)
+  {
+    /* Test if an product of this id is known */
+    struct TALER_MERCHANTDB_ProductDetails epd;
+
+    if (GNUNET_OK !=
+        TMH_db->start (TMH_db->cls,
+                       "/post products"))
+    {
+      GNUNET_break (0);
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_PRODUCTS_POST_DB_START_ERROR,
+                                         "Failed to start transaction");
+    }
+    qs = TMH_db->lookup_product (TMH_db->cls,
+                                 mi->settings.id,
+                                 product_id,
+                                 &epd);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      /* Clean up and fail hard */
+      break;
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      /* restart transaction */
+      goto retry;
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      /* Good, we can proceed! */
+      break;
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      /* idempotency check: is epd == pd? */
+      if (products_equal (&pd,
+                          &epd))
+      {
+        TMH_db->rollback (TMH_db->cls);
+        GNUNET_JSON_parse_free (spec);
+        return TALER_MHD_reply_static (connection,
+                                       MHD_HTTP_NO_CONTENT,
+                                       NULL,
+                                       NULL,
+                                       0);
+      }
+      else
+      {
+        TMH_db->rollback (TMH_db->cls);
+        GNUNET_JSON_parse_free (spec);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_CONFLICT,
+                                           
TALER_EC_PRODUCTS_POST_CONFLICT_PRODUCT_EXISTS,
+                                           "different product exists under 
this product ID");
+      }
+    }
+
+    qs = TMH_db->insert_product (TMH_db->cls,
+                                 mi->settings.id,
+                                 product_id,
+                                 &pd);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      goto retry;
+    qs = TMH_db->commit (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+      break;
+retry:
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      continue;
+    }
+  }
+  GNUNET_JSON_parse_free (spec);
+  if (qs < 0)
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+                                       ?
+                                       
TALER_EC_PRODUCTS_POST_DB_COMMIT_SOFT_ERROR
+                                       :
+                                       
TALER_EC_PRODUCTS_POST_DB_COMMIT_HARD_ERROR,
+                                       "Failed to commit transaction");
+  return TALER_MHD_reply_static (connection,
+                                 MHD_HTTP_NO_CONTENT,
+                                 NULL,
+                                 NULL,
+                                 0);
+}
+
+
+/* end of taler-merchant-httpd_private-post-products.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-products.h 
b/src/backend/taler-merchant-httpd_private-post-products.h
new file mode 100644
index 0000000..efcbf5f
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-products.h
@@ -0,0 +1,43 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-products.h
+ * @brief implementing POST /products request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Generate a product entry in our inventory.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_products (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc);
+
+#endif
diff --git 
a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c 
b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
new file mode 100644
index 0000000..1c28173
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
@@ -0,0 +1,228 @@
+/*
+  This file is part of TALER
+  (C) 2014-2020 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_private-post-reserves-ID-authorize-tip.c
+ * @brief implement API for authorizing tips to be paid to visitors
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_mhd.h"
+#include "taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h"
+
+
+/**
+ * Handle a "tip-authorize" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @param reserve_pub reserve to use, or NULL for "any"
+ * @return MHD result code
+ */
+static MHD_RESULT
+authorize_tip (const struct TMH_RequestHandler *rh,
+               struct MHD_Connection *connection,
+               struct TMH_HandlerContext *hc,
+               const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+  enum TALER_ErrorCode ec;
+  struct GNUNET_TIME_Absolute expiration;
+  struct GNUNET_HashCode tip_id;
+  const char *justification;
+  const char *next_url;
+  struct TALER_Amount amount;
+  {
+    struct GNUNET_JSON_Specification spec[] = {
+      TALER_JSON_spec_amount ("amount", &amount),
+      GNUNET_JSON_spec_string ("justification", &justification),
+      GNUNET_JSON_spec_string ("next_url", &next_url),
+      GNUNET_JSON_spec_end ()
+    };
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_YES != res)
+    {
+      GNUNET_break_op (0);
+      return (GNUNET_NO == res)
+             ? MHD_YES
+             : MHD_NO;
+    }
+  }
+  TMH_db->preflight (TMH_db->cls);
+  ec = TMH_db->authorize_tip (TMH_db->cls,
+                              hc->instance->settings.id,
+                              reserve_pub,
+                              &amount,
+                              justification,
+                              next_url,
+                              &tip_id,
+                              &expiration);
+  /* handle errors */
+  if (TALER_EC_NONE != ec)
+  {
+    unsigned int rc;
+    const char *msg;
+
+    switch (ec)
+    {
+    case TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS:
+      rc = MHD_HTTP_PRECONDITION_FAILED;
+      msg = "Failed to approve tip: merchant has insufficient tipping funds";
+      break;
+    case TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED:
+      msg = "Failed to approve tip: merchant's tipping reserve expired";
+      rc = MHD_HTTP_GONE;
+      break;
+    case TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN:
+      msg = "Failed to approve tip: merchant's tipping reserve does not exist";
+      rc = MHD_HTTP_SERVICE_UNAVAILABLE;
+      break;
+    case TALER_EC_TIP_AUTHORIZE_DB_RESERVE_NOT_FOUND:
+      msg = "Failed to approve tip: merchant's tipping reserve does not exist";
+      rc = MHD_HTTP_NOT_FOUND;
+      break;
+    default:
+      rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      msg = "Failed to approve tip: internal server error";
+      break;
+    }
+
+    return TALER_MHD_reply_with_error (connection,
+                                       rc,
+                                       ec,
+                                       msg);
+  }
+
+  /* generate success response */
+  {
+    char *taler_tip_uri;
+    const char *host;
+    const char *forwarded_host;
+    const char *uri_path;
+    struct GNUNET_CRYPTO_HashAsciiEncoded hash_enc;
+    MHD_RESULT res;
+
+    host = MHD_lookup_connection_value (connection,
+                                        MHD_HEADER_KIND,
+                                        "Host");
+    forwarded_host = MHD_lookup_connection_value (connection,
+                                                  MHD_HEADER_KIND,
+                                                  "X-Forwarded-Host");
+
+    uri_path = MHD_lookup_connection_value (connection,
+                                            MHD_HEADER_KIND,
+                                            "X-Forwarded-Prefix");
+    if (NULL != forwarded_host)
+      host = forwarded_host;
+    if (NULL == host)
+    {
+      /* Should never happen, at last the host header should be defined */
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_INTERNAL_INVARIANT_FAILURE,
+                                         "unable to identify backend host");
+    }
+
+    GNUNET_CRYPTO_hash_to_enc (&tip_id,
+                               &hash_enc);
+    GNUNET_assert (0 < GNUNET_asprintf (&taler_tip_uri,
+                                        "taler://tip/%s/%s%sinstances/%s/%s",
+                                        host,
+                                        (NULL == uri_path) ? "" : uri_path,
+                                        (NULL == uri_path) ? "" : "/",
+                                        hc->instance->settings.id,
+                                        hash_enc.encoding));
+    GNUNET_TIME_round_abs (&expiration);
+    res = TALER_MHD_reply_json_pack (connection,
+                                     MHD_HTTP_OK,
+                                     "{s:s, s:s, s:o}",
+                                     "tip_id",
+                                     hash_enc.encoding,
+                                     "tip_redirect_url",
+                                     taler_tip_uri,
+                                     "tip_expiration",
+                                     GNUNET_JSON_from_time_abs (expiration));
+    GNUNET_free (taler_tip_uri);
+    return res;
+  }
+}
+
+
+/**
+ * Handle a "/reserves/$ID/tip-authorize" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_reserves_ID_authorize_tip (const struct TMH_RequestHandler 
*rh,
+                                            struct MHD_Connection *connection,
+                                            struct TMH_HandlerContext *hc)
+{
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (hc->infix,
+                                     strlen (hc->infix),
+                                     &reserve_pub,
+                                     sizeof (reserve_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_RESERVES_INVALID_RESERVE_PUB,
+                                       "reserve public key malformed");
+  }
+  return authorize_tip (rh,
+                        connection,
+                        hc,
+                        &reserve_pub);
+}
+
+
+/**
+ * Handle a POST "/tips" request.
+ * Here the client does not specify the reserve public key, so we
+ * are free to pick "any" available reserve.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_tips (const struct TMH_RequestHandler *rh,
+                       struct MHD_Connection *connection,
+                       struct TMH_HandlerContext *hc)
+{
+  return authorize_tip (rh,
+                        connection,
+                        hc,
+                        NULL);
+}
+
+
+/* end of taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c */
diff --git 
a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h 
b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h
new file mode 100644
index 0000000..297a53d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h
@@ -0,0 +1,57 @@
+/*
+  This file is part of TALER
+  (C) 2017, 2020 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_private-post-reserves-ID-authorize-tip.h
+ * @brief headers for /reserves/ID/tip-authorize
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_ID_AUTHORIZE_TIP_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_ID_AUTHORIZE_TIP_H
+#include <microhttpd.h>
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a "/reserves/$ID/tip-authorize" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_reserves_ID_authorize_tip (const struct TMH_RequestHandler 
*rh,
+                                            struct MHD_Connection *connection,
+                                            struct TMH_HandlerContext *hc);
+
+
+/**
+ * Handle a POST "/tips" request.
+ * Here the client does not specify the reserve public key, so we
+ * are free to pick "any" available reserve.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_tips (const struct TMH_RequestHandler *rh,
+                       struct MHD_Connection *connection,
+                       struct TMH_HandlerContext *hc);
+
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.c 
b/src/backend/taler-merchant-httpd_private-post-reserves.c
new file mode 100644
index 0000000..3e72650
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-reserves.c
@@ -0,0 +1,325 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-reserves.c
+ * @brief implementing POST /reserves request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_private-post-reserves.h"
+#include "taler-merchant-httpd_reserves.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Information we keep for an individual call to the POST /reserves handler.
+ */
+struct PostReserveContext
+{
+
+  /**
+   * Stored in a DLL.
+   */
+  struct PostReserveContext *next;
+
+  /**
+   * Stored in a DLL.
+   */
+  struct PostReserveContext *prev;
+
+  /**
+   * Array with @e coins_cnt coins we are despositing.
+   */
+  struct DepositConfirmation *dc;
+
+  /**
+   * MHD connection to return to
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Details about the client's request.
+   */
+  struct TMH_HandlerContext *hc;
+
+  /**
+   * URL of the exchange.
+   */
+  const char *exchange_url;
+
+  /**
+   * URI of the exchange where the payment needs to be made to.
+   */
+  char *payto_uri;
+
+  /**
+   * Handle for contacting the exchange.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Initial balance of the reserve.
+   */
+  struct TALER_Amount initial_balance;
+
+  /**
+   * When will the reserve expire.
+   */
+  struct GNUNET_TIME_Absolute reserve_expiration;
+
+  /**
+   * Which HTTP status should we return?
+   */
+  unsigned int http_status;
+
+  /**
+   * Which error code should we return?
+   */
+  enum TALER_ErrorCode ec;
+
+  /**
+   * Are we suspended?
+   */
+  bool suspended;
+};
+
+
+/**
+ * Stored in a DLL.
+ */
+static struct PostReserveContext *rc_head;
+
+/**
+ * Stored in a DLL.
+ */
+static struct PostReserveContext *rc_tail;
+
+
+/**
+ * Force all post reserve contexts to be resumed as we are about
+ * to shut down MHD.
+ */
+void
+TMH_force_rc_resume ()
+{
+  for (struct PostReserveContext *rc = rc_head;
+       NULL != rc;
+       rc = rc->next)
+  {
+    if (rc->suspended)
+    {
+      rc->suspended = false;
+      MHD_resume_connection (rc->connection);
+      GNUNET_CONTAINER_DLL_remove (rc_head,
+                                   rc_tail,
+                                   rc);
+    }
+    if (NULL != rc->fo)
+    {
+      TMH_EXCHANGES_find_exchange_cancel (rc->fo);
+      rc->fo = NULL;
+    }
+  }
+}
+
+
+/**
+ * Custom cleanup routine for a `struct PostReserveContext`.
+ *
+ * @param cls the `struct PostReserveContext` to clean up.
+ */
+static void
+reserve_context_cleanup (void *cls)
+{
+  struct PostReserveContext *rc = cls;
+
+  if (NULL != rc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (rc->fo);
+    rc->fo = NULL;
+  }
+  GNUNET_assert (! rc->suspended);
+  GNUNET_free (rc->payto_uri);
+  GNUNET_free (rc);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @param cls closure with our `struct PostReserveContext *`
+ * @param hr HTTP response details
+ * @param payto_uri URI of the exchange for the wire transfer, NULL on errors
+ * @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 true if this exchange is trusted by config
+ */
+static void
+handle_exchange (void *cls,
+                 const struct TALER_EXCHANGE_HttpResponse *hr,
+                 struct TALER_EXCHANGE_Handle *eh,
+                 const char *payto_uri,
+                 const struct TALER_Amount *wire_fee,
+                 bool exchange_trusted)
+{
+  struct PostReserveContext *rc = cls;
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  rc->fo = NULL;
+  rc->suspended = false;
+  MHD_resume_connection (rc->connection);
+  GNUNET_CONTAINER_DLL_remove (rc_head,
+                               rc_tail,
+                               rc);
+  if (NULL == hr)
+  {
+    rc->ec = TALER_EC_TIMEOUT;
+    rc->http_status = MHD_HTTP_REQUEST_TIMEOUT;
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    return;
+  }
+  keys = TALER_EXCHANGE_get_keys (eh);
+  if (NULL == keys)
+  {
+    rc->ec = TALER_EC_KEYS_INVALID;
+    rc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    return;
+  }
+  if (NULL == payto_uri)
+  {
+    rc->ec = TALER_EC_RESERVES_POST_UNSUPPORTED_WIRE_METHOD;
+    rc->http_status = MHD_HTTP_CONFLICT;
+    TMH_trigger_daemon ();   /* we resumed, kick MHD */
+    return;
+  }
+  rc->reserve_expiration
+    = GNUNET_TIME_relative_to_absolute (keys->reserve_closing_delay);
+  rc->payto_uri = GNUNET_strdup (payto_uri);
+  TMH_trigger_daemon ();   /* we resumed, kick MHD */
+}
+
+
+/**
+ * Generate a reserve, given its keys and balance.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_reserves (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc)
+{
+  struct PostReserveContext *rc = hc->ctx;
+  struct TMH_MerchantInstance *mi = hc->instance;
+
+  GNUNET_assert (NULL != mi);
+  if (NULL == rc)
+  {
+    const char *wire_method;
+
+    rc = GNUNET_new (struct PostReserveContext);
+    rc->connection = connection;
+    rc->hc = hc;
+    hc->ctx = rc;
+    hc->cc = &reserve_context_cleanup;
+
+    {
+      enum GNUNET_GenericReturnValue res;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_string ("exchange_url",
+                                 &rc->exchange_url),
+        GNUNET_JSON_spec_string ("wire_method",
+                                 &wire_method),
+        TALER_JSON_spec_amount ("initial_balance",
+                                &rc->initial_balance),
+        GNUNET_JSON_spec_end ()
+      };
+      res = TALER_MHD_parse_json_data (connection,
+                                       hc->request_body,
+                                       spec);
+      if (GNUNET_OK != res)
+        return (GNUNET_NO == res)
+               ? MHD_YES
+               : MHD_NO;
+    }
+    rc->fo = TMH_EXCHANGES_find_exchange (rc->exchange_url,
+                                          wire_method,
+                                          GNUNET_NO,
+                                          &handle_exchange,
+                                          rc);
+    rc->suspended = true;
+    GNUNET_CONTAINER_DLL_insert (rc_head,
+                                 rc_tail,
+                                 rc);
+    MHD_suspend_connection (connection);
+    return MHD_YES;
+  }
+
+  GNUNET_assert (! rc->suspended);
+  if (NULL == rc->payto_uri)
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       rc->http_status,
+                                       rc->ec,
+                                       "Exchange does not support wire 
method");
+  }
+  {
+    struct TALER_ReservePublicKeyP reserve_pub;
+    struct TALER_ReservePrivateKeyP reserve_priv;
+    enum GNUNET_DB_QueryStatus qs;
+
+    GNUNET_CRYPTO_eddsa_key_create (&reserve_priv.eddsa_priv);
+    GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv.eddsa_priv,
+                                        &reserve_pub.eddsa_pub);
+    qs = TMH_db->insert_reserve (TMH_db->cls,
+                                 mi->settings.id,
+                                 &reserve_priv,
+                                 &reserve_pub,
+                                 rc->exchange_url,
+                                 &rc->initial_balance,
+                                 rc->reserve_expiration);
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    TMH_RESERVES_check (mi->settings.id,
+                        rc->exchange_url,
+                        &reserve_pub,
+                        &rc->initial_balance);
+    if (qs < 0)
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_RESERVES_POST_DB_COMMIT_HARD_ERROR,
+                                         "Failed to commit transaction");
+    return TALER_MHD_reply_json_pack (connection,
+                                      MHD_HTTP_OK,
+                                      "{s:o,s:s}",
+                                      "reserve_pub",
+                                      GNUNET_JSON_from_data_auto 
(&reserve_pub),
+                                      "payto_uri",
+                                      rc->payto_uri);
+  }
+}
+
+
+/* end of taler-merchant-httpd_private-post-reserves.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.h 
b/src/backend/taler-merchant-httpd_private-post-reserves.h
new file mode 100644
index 0000000..2c35648
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-reserves.h
@@ -0,0 +1,50 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_private-post-reserves.h
+ * @brief implementing POST /reserves request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_RESERVES_H
+#include "taler-merchant-httpd.h"
+
+/**
+ * Force all post reserve contexts to be resumed as we are about
+ * to shut down MHD.
+ */
+void
+TMH_force_rc_resume ();
+
+
+/**
+ * Generate a reserve entry in our inventory.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_reserves (const struct TMH_RequestHandler *rh,
+                           struct MHD_Connection *connection,
+                           struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c 
b/src/backend/taler-merchant-httpd_private-post-transfers.c
new file mode 100644
index 0000000..45b6742
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-transfers.c
@@ -0,0 +1,1059 @@
+/*
+  This file is part of TALER
+  (C) 2014-2020 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_private-post-transfers.c
+ * @brief implement API for registering wire transfers
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd_auditors.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_private-post-transfers.h"
+
+
+/**
+ * How long to wait before giving up processing with the exchange?
+ */
+#define TRANSFER_TIMEOUT (GNUNET_TIME_relative_multiply ( \
+                            GNUNET_TIME_UNIT_SECONDS, \
+                            30))
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * Context used for handing POST /private/transfers requests.
+ */
+struct PostTransfersContext
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PostTransfersContext *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PostTransfersContext *prev;
+
+  /**
+   * Argument for the /wire/transfers request.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+  /**
+   * Amount of the wire transfer.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * URL of the exchange.
+   */
+  const char *exchange_url;
+
+  /**
+   * payto:// URI used for the transfer.
+   */
+  const char *payto_uri;
+
+  /**
+   * Master public key of the exchange at @e exchange_url.
+   */
+  struct TALER_MasterPublicKeyP master_pub;
+
+  /**
+   * Handle for the /wire/transfers request.
+   */
+  struct TALER_EXCHANGE_TransfersGetHandle *wdh;
+
+  /**
+   * For which merchant instance is this tracking request?
+   */
+  struct TMH_HandlerContext *hc;
+
+  /**
+   * HTTP connection we are handling.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Response to return upon resume.
+   */
+  struct MHD_Response *response;
+
+  /**
+   * Handle for operation to lookup /keys (and auditors) from
+   * the exchange used for this transaction; NULL if no operation is
+   * pending.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Task run on timeout.
+   */
+  struct GNUNET_SCHEDULER_Task *timeout_task;
+
+  /**
+   * Pointer to the detail that we are currently
+   * checking in #check_transfer().
+   */
+  const struct TALER_TrackTransferDetails *current_detail;
+
+  /**
+   * Which transaction detail are we currently looking at?
+   */
+  unsigned int current_offset;
+
+  /**
+   * Response code to return.
+   */
+  unsigned int response_code;
+
+  /**
+   * #GNUNET_NO if we did not find a matching coin.
+   * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
+   * #GNUNET_OK if we did find a matching coin.
+   */
+  int check_transfer_result;
+};
+
+
+/**
+ * Head of list of suspended requests.
+ */
+static struct PostTransfersContext *ptc_head;
+
+/**
+ * Tail of list of suspended requests.
+ */
+static struct PostTransfersContext *ptc_tail;
+
+
+/**
+ * We are shutting down, force resume of all POST /transfers requests.
+ */
+void
+TMH_force_post_transfers_resume ()
+{
+  struct PostTransfersContext *ptc;
+
+  while (NULL != (ptc = ptc_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (ptc_head,
+                                 ptc_tail,
+                                 ptc);
+    MHD_resume_connection (ptc->connection);
+    if (NULL != ptc->timeout_task)
+    {
+      GNUNET_SCHEDULER_cancel (ptc->timeout_task);
+      ptc->timeout_task = NULL;
+    }
+  }
+}
+
+
+/**
+ * Resume the given /track/transfer operation and send the given response.
+ * Stores the response in the @a ptc and signals MHD to resume
+ * the connection.  Also ensures MHD runs immediately.
+ *
+ * @param ptc transfer tracking context
+ * @param response_code response code to use
+ * @param response response data to send back
+ */
+static void
+resume_transfer_with_response (struct PostTransfersContext *ptc,
+                               unsigned int response_code,
+                               struct MHD_Response *response)
+{
+  ptc->response_code = response_code;
+  ptc->response = response;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Resuming POST /transfers handling as exchange interaction is 
done (%u)\n",
+              response_code);
+  if (NULL != ptc->timeout_task)
+  {
+    GNUNET_SCHEDULER_cancel (ptc->timeout_task);
+    ptc->timeout_task = NULL;
+  }
+  GNUNET_CONTAINER_DLL_remove (ptc_head,
+                               ptc_tail,
+                               ptc);
+  MHD_resume_connection (ptc->connection);
+  TMH_trigger_daemon ();   /* we resumed, kick MHD */
+}
+
+
+/**
+ * Resume the given POST /transfers operation with an error.
+ *
+ * @param ptc transfer tracking context
+ * @param response_code response code to use
+ * @param ec error code to use
+ * @param hint hint text to provide
+ */
+static void
+resume_transfer_with_error (struct PostTransfersContext *ptc,
+                            unsigned int response_code,
+                            enum TALER_ErrorCode ec,
+                            const char *hint)
+{
+  resume_transfer_with_response (ptc,
+                                 response_code,
+                                 TALER_MHD_make_error (ec,
+                                                       hint));
+}
+
+
+/**
+ * Custom cleanup routine for a `struct PostTransfersContext`.
+ *
+ * @param cls the `struct PostTransfersContext` to clean up.
+ */
+static void
+transfer_cleanup (void *cls)
+{
+  struct PostTransfersContext *ptc = cls;
+
+  if (NULL != ptc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
+    ptc->fo = NULL;
+  }
+  if (NULL != ptc->timeout_task)
+  {
+    GNUNET_SCHEDULER_cancel (ptc->timeout_task);
+    ptc->timeout_task = NULL;
+  }
+  if (NULL != ptc->wdh)
+  {
+    TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
+    ptc->wdh = NULL;
+  }
+  GNUNET_free (ptc);
+}
+
+
+/**
+ * This function checks that the information about the coin which
+ * was paid back by _this_ wire transfer matches what _we_ (the merchant)
+ * knew about this coin.
+ *
+ * @param cls closure with our `struct PostTransfersContext *`
+ * @param transaction_id of the contract
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param amount_with_fee amount the exchange will transfer for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param h_wire hash of merchant's wire details
+ * @param deposit_timestamp when did the exchange receive the deposit
+ * @param refund_deadline until when are refunds allowed
+ * @param exchange_sig signature by the exchange
+ * @param exchange_pub exchange signing key used for @a exchange_sig
+ */
+static void
+check_transfer (void *cls,
+                const char *exchange_url,
+                const struct TALER_Amount *amount_with_fee,
+                const struct TALER_Amount *deposit_fee,
+                const struct TALER_Amount *refund_fee,
+                const struct TALER_Amount *wire_fee,
+                const struct GNUNET_HashCode *h_wire,
+                struct GNUNET_TIME_Absolute deposit_timestamp,
+                struct GNUNET_TIME_Absolute refund_deadline,
+                const struct TALER_ExchangeSignatureP *exchange_sig,
+                const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+  struct PostTransfersContext *ptc = cls;
+  const struct TALER_TrackTransferDetails *ttd = ptc->current_detail;
+
+  if (GNUNET_SYSERR == ptc->check_transfer_result)
+    return;   /* already had a serious issue; odd that we're called more than 
once as well... */
+  if ( (0 != TALER_amount_cmp (amount_with_fee,
+                               &ttd->coin_value)) ||
+       (0 != TALER_amount_cmp (deposit_fee,
+                               &ttd->coin_fee)) )
+  {
+    /* Disagreement between the exchange and us about how much this
+       coin is worth! */
+    GNUNET_break_op (0);
+    ptc->check_transfer_result = GNUNET_SYSERR;
+    /* Build the `TrackTransferConflictDetails` */
+    ptc->response_code = MHD_HTTP_ACCEPTED;
+    ptc->response
+      = TALER_MHD_make_json_pack (
+          "{s:I, s:s, s:s, s:o, s:o,"
+          " s:I, s:o, s:o, s:o, s:o,"
+          " s:o, s:o, s:o, s:o, s:o }",
+          "code",
+          (json_int_t) TALER_EC_POST_TRANSFERS_CONFLICTING_REPORTS,
+          "hint",
+          "disagreement about deposit valuation",
+          "exchange_url",
+          exchange_url,
+          "deposit_timestamp",
+          GNUNET_JSON_from_time_abs (deposit_timestamp),
+          "refund_deadline",
+          GNUNET_JSON_from_time_abs (refund_deadline),
+          /* first block of 5 */
+          "conflict_offset",
+          (json_int_t) ptc->current_offset,
+          "coin_pub",
+          GNUNET_JSON_from_data_auto (&ttd->coin_pub),
+          "h_wire",
+          GNUNET_JSON_from_data_auto (h_wire),
+          "deposit_exchange_sig",
+          GNUNET_JSON_from_data_auto (exchange_sig),
+          "deposit_exchange_pub",
+          GNUNET_JSON_from_data_auto (exchange_pub),
+          /* first block of 5 */
+          "h_contract_terms",
+          GNUNET_JSON_from_data_auto (&ttd->h_contract_terms),
+          "amount_with_fee",
+          TALER_JSON_from_amount (amount_with_fee),
+          "coin_value",
+          TALER_JSON_from_amount (&ttd->coin_value),
+          "coin_fee",
+          TALER_JSON_from_amount (&ttd->coin_fee),
+          "deposit_fee",
+          TALER_JSON_from_amount (deposit_fee));
+    return;
+  }
+  ptc->check_transfer_result = GNUNET_OK;
+}
+
+
+/**
+ * Check that the given @a wire_fee is what the @a exchange_pub should charge
+ * at the @a execution_time.  If the fee is correct (according to our
+ * database), return #GNUNET_OK.  If we do not have the fee structure in our
+ * DB, we just accept it and return #GNUNET_NO; if we have proof that the fee
+ * is bogus, we respond with the proof to the client and return
+ * #GNUNET_SYSERR.
+ *
+ * @param ptc context of the transfer to respond to
+ * @param execution_time time of the wire transfer
+ * @param wire_fee fee claimed by the exchange
+ * @return #GNUNET_SYSERR if we returned hard proof of
+ *   missbehavior from the exchange to the client
+ */
+static int
+check_wire_fee (struct PostTransfersContext *ptc,
+                struct GNUNET_TIME_Absolute execution_time,
+                const struct TALER_Amount *wire_fee)
+{
+  struct TALER_Amount expected_fee;
+  struct TALER_Amount closing_fee;
+  struct TALER_MasterSignatureP master_sig;
+  struct GNUNET_TIME_Absolute start_date;
+  struct GNUNET_TIME_Absolute end_date;
+  enum GNUNET_DB_QueryStatus qs;
+  char *wire_method;
+
+  wire_method = TALER_payto_get_method (ptc->payto_uri);
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->lookup_wire_fee (TMH_db->cls,
+                                &ptc->master_pub,
+                                wire_method,
+                                execution_time,
+                                &expected_fee,
+                                &closing_fee,
+                                &start_date,
+                                &end_date,
+                                &master_sig);
+  if (0 >= qs)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to find wire fee for `%s' and method `%s' at %s in DB, 
accepting blindly that the fee is %s\n",
+                TALER_B2S (&ptc->master_pub),
+                wire_method,
+                GNUNET_STRINGS_absolute_time_to_string (execution_time),
+                TALER_amount2s (wire_fee));
+    GNUNET_free (wire_method);
+    return GNUNET_NO;
+  }
+  if (0 <= TALER_amount_cmp (&expected_fee,
+                             wire_fee))
+  {
+    GNUNET_free (wire_method);
+    return GNUNET_OK;   /* expected_fee >= wire_fee */
+  }
+  /* Wire fee check failed, export proof to client */
+  ptc->response_code = MHD_HTTP_ACCEPTED;
+  ptc->response =
+    TALER_MHD_make_json_pack (
+      "{s:I, s:s, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}",
+      "code", (json_int_t) TALER_EC_POST_TRANSFERS_JSON_BAD_WIRE_FEE,
+      "hint", "exchange provided conflicting wire fee information",
+      "wire_fee", TALER_JSON_from_amount (wire_fee),
+      "execution_time", GNUNET_JSON_from_time_abs (execution_time),
+      "expected_wire_fee", TALER_JSON_from_amount (&expected_fee),
+      "expected_closing_fee", TALER_JSON_from_amount (&closing_fee),
+      "start_date", GNUNET_JSON_from_time_abs (start_date),
+      "end_date", GNUNET_JSON_from_time_abs (end_date),
+      "master_sig", GNUNET_JSON_from_data_auto (&master_sig),
+      "master_pub", GNUNET_JSON_from_data_auto (&ptc->master_pub));
+  GNUNET_free (wire_method);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called with detailed wire transfer data, including all
+ * of the coin transactions that were combined into the wire transfer.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param td transfer data
+ */
+static void
+wire_transfer_cb (void *cls,
+                  const struct TALER_EXCHANGE_HttpResponse *hr,
+                  const struct TALER_EXCHANGE_TransferData *td)
+{
+  struct PostTransfersContext *ptc = cls;
+  const char *instance_id = ptc->hc->instance->settings.id;
+  enum GNUNET_DB_QueryStatus qs;
+
+  ptc->wdh = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Got response code %u from exchange for GET /transfers/$WTID\n",
+              hr->http_status);
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    resume_transfer_with_response (
+      ptc,
+      MHD_HTTP_FAILED_DEPENDENCY,
+      TALER_MHD_make_json_pack (
+        "{s:I, s:I, s:I, s:O}",
+        "code", (json_int_t) TALER_EC_POST_TRANSFERS_EXCHANGE_ERROR,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply));
+    return;
+  }
+
+  for (unsigned int r = 0; r<MAX_RETRIES; r++)
+  {
+    TMH_db->preflight (TMH_db->cls);
+    if (GNUNET_OK !=
+        TMH_db->start (TMH_db->cls,
+                       "insert transaction details"))
+    {
+      GNUNET_break (0);
+      resume_transfer_with_error (ptc,
+                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                  
TALER_EC_POST_TRANSFERS_DB_STORE_TRANSFER_ERROR,
+                                  "could not start transaction");
+      return;
+    }
+    /* Ok, exchange answer is acceptable, store it */
+    qs = TMH_db->insert_transfer_details (TMH_db->cls,
+                                          instance_id,
+                                          ptc->exchange_url,
+                                          ptc->payto_uri,
+                                          &ptc->wtid,
+                                          td);
+    if (0 > qs)
+      goto retry;
+    qs = TMH_db->commit (TMH_db->cls);
+retry:
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      resume_transfer_with_error (
+        ptc,
+        MHD_HTTP_INTERNAL_SERVER_ERROR,
+        TALER_EC_POST_TRANSFERS_DB_STORE_TRANSFER_ERROR,
+        "failed to commit transaction to local database");
+      return;
+    }
+    if (0 <= qs)
+      break; /* success! */
+  }
+  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    /* Always report on hard error as well to enable diagnostics */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    resume_transfer_with_error (
+      ptc,
+      MHD_HTTP_INTERNAL_SERVER_ERROR,
+      TALER_EC_POST_TRANSFERS_DB_STORE_TRANSFER_ERROR,
+      "repeated serialization failures trying to commit transaction to local 
database");
+    return;
+  }
+
+  /* resume processing, main function will build the response */
+  resume_transfer_with_response (ptc,
+                                 0,
+                                 NULL);
+}
+
+
+/**
+ * Function called with the result of our exchange lookup.
+ *
+ * @param cls the `struct PostTransfersContext`
+ * @param hr HTTP response details
+ * @param eh NULL if exchange was not found to be acceptable
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee NULL (we did not specify a wire method)
+ * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ */
+static void
+process_transfer_with_exchange (void *cls,
+                                const struct TALER_EXCHANGE_HttpResponse *hr,
+                                struct TALER_EXCHANGE_Handle *eh,
+                                const char *payto_uri,
+                                const struct TALER_Amount *wire_fee,
+                                bool exchange_trusted)
+{
+  struct PostTransfersContext *ptc = cls;
+
+  (void) payto_uri;
+  (void) exchange_trusted;
+  ptc->fo = NULL;
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    /* The request failed somehow */
+    GNUNET_break_op (0);
+    resume_transfer_with_response (
+      ptc,
+      MHD_HTTP_FAILED_DEPENDENCY,
+      TALER_MHD_make_json_pack (
+        (NULL != hr->reply)
+        ? "{s:s, s:I, s:I, s:I, s:O}"
+        : "{s:s, s:I, s:I, s:I}",
+        "hint", "failed to obtain meta-data from exchange",
+        "code", (json_int_t) TALER_EC_POST_TRANSFERS_EXCHANGE_KEYS_FAILURE,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_reply", hr->reply));
+    return;
+  }
+
+  /* keep master key for later */
+  {
+    const struct TALER_EXCHANGE_Keys *keys;
+
+    keys = TALER_EXCHANGE_get_keys (eh);
+    if (NULL == keys)
+    {
+      GNUNET_break (0);
+      resume_transfer_with_error (ptc,
+                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                  
TALER_EC_POST_TRANSFERS_EXCHANGE_KEYS_FAILURE,
+                                  "failed to get keys");
+      return;
+    }
+    ptc->master_pub = keys->master_pub;
+  }
+
+  ptc->wdh = TALER_EXCHANGE_transfers_get (eh,
+                                           &ptc->wtid,
+                                           &wire_transfer_cb,
+                                           ptc);
+  if (NULL == ptc->wdh)
+  {
+    GNUNET_break (0);
+    resume_transfer_with_error (ptc,
+                                MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                TALER_EC_POST_TRANSFERS_REQUEST_ERROR,
+                                "failed to run GET /transfers/ on exchange");
+  }
+}
+
+
+/**
+ * Now we want to double-check that any (Taler coin) deposit which is
+ * accounted into _this_ wire transfer, does exist into _our_ database.  This
+ * is the rationale: if the exchange paid us for it, we must have received it
+ * _beforehands_!
+ *
+ * @param cls a `struct PostTransfersContext`
+ * @param current_offset at which offset in the exchange's reply are the @a ttd
+ * @param ttd details about an aggregated transfer (to check)
+ */
+static void
+verify_exchange_claim_cb (void *cls,
+                          unsigned int current_offset,
+                          const struct TALER_TrackTransferDetails *ttd)
+{
+  struct PostTransfersContext *ptc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  if (0 != ptc->response_code)
+    return; /* already encountered an error */
+  ptc->current_offset = current_offset;
+  ptc->current_detail = ttd;
+  /* Set the coin as "never seen" before. */
+  ptc->check_transfer_result = GNUNET_NO;
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->lookup_deposits_by_contract_and_coin (
+    TMH_db->cls,
+    ptc->hc->instance->settings.id,
+    &ttd->h_contract_terms,
+    &ttd->coin_pub,
+    &check_transfer,
+    ptc);
+  if (0 > qs)
+  {
+    /* single, read-only SQL statements should never cause
+       serialization problems */
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    /* Always report on hard error as well to enable diagnostics */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    ptc->response
+      = TALER_MHD_make_error (TALER_EC_POST_TRANSFERS_DB_FETCH_DEPOSIT_ERROR,
+                              "failed to obtain deposit data from local 
database");
+    return;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    /* The exchange says we made this deposit, but WE do not
+       recall making it (corrupted / unreliable database?)!
+       Well, let's say thanks and accept the money! */
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to find payment data in DB\n");
+    ptc->check_transfer_result = GNUNET_OK;
+  }
+  if (GNUNET_NO == ptc->check_transfer_result)
+  {
+    /* Internal error: how can we have called #check_transfer()
+       but still have no result? */
+    GNUNET_break (0);
+    ptc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    ptc->response =
+      TALER_MHD_make_error (TALER_EC_POST_TRANSFERS_DB_INTERNAL_LOGIC_ERROR,
+                            "internal logic error");
+    return;
+  }
+  if (GNUNET_SYSERR == ptc->check_transfer_result)
+  {
+    /* #check_transfer() failed, report conflict! */
+    GNUNET_break_op (0);
+    GNUNET_assert (NULL != ptc->response);
+    return;
+  }
+}
+
+
+/**
+ * Represents an entry in the table used to sum up
+ * individual deposits for each h_contract_terms/order_id
+ * (as the exchange gives us per coin, and we return
+ * per order).
+ */
+struct Entry
+{
+
+  /**
+   * Order of the entry.
+   */
+  char *order_id;
+
+  /**
+   * Sum accumulator for deposited value.
+   */
+  struct TALER_Amount deposit_value;
+
+  /**
+   * Sum accumulator for deposit fee.
+   */
+  struct TALER_Amount deposit_fee;
+
+};
+
+
+/**
+ * Function called with information about a wire transfer identifier.
+ * Generate a response array based on the given information.
+ *
+ * @param cls closure, a hashmap to update
+ * @param order_id the order to which the deposits belong
+ * @param deposit_value the amount deposited under @a order_id
+ * @param deposit_fee the fee charged for @a deposit_value
+ */
+static void
+transfer_summary_cb (void *cls,
+                     const char *order_id,
+                     const struct TALER_Amount *deposit_value,
+                     const struct TALER_Amount *deposit_fee)
+{
+  struct GNUNET_CONTAINER_MultiHashMap *map = cls;
+  struct Entry *current_entry;
+  struct GNUNET_HashCode h_key;
+
+  GNUNET_CRYPTO_hash_from_string (order_id,
+                                  &h_key);
+  current_entry = GNUNET_CONTAINER_multihashmap_get (map,
+                                                     &h_key);
+  if (NULL != current_entry)
+  {
+    /* The map already knows this order, do aggregation */
+    GNUNET_assert ( (0 <=
+                     TALER_amount_add (&current_entry->deposit_value,
+                                       &current_entry->deposit_value,
+                                       deposit_value)) &&
+                    (0 <=
+                     TALER_amount_add (&current_entry->deposit_fee,
+                                       &current_entry->deposit_fee,
+                                       deposit_fee)) );
+  }
+  else
+  {
+    /* First time in the map for this h_contract_terms*/
+    current_entry = GNUNET_new (struct Entry);
+    current_entry->deposit_value = *deposit_value;
+    current_entry->deposit_fee = *deposit_fee;
+    current_entry->order_id = GNUNET_strdup (order_id);
+    GNUNET_assert (GNUNET_SYSERR !=
+                   GNUNET_CONTAINER_multihashmap_put (map,
+                                                      &h_key,
+                                                      current_entry,
+                                                      
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  }
+}
+
+
+/**
+ * Callback that frees all the elements in the hashmap, and @a cls
+ * is non-NULL, appends them as JSON to the array
+ *
+ * @param cls closure, NULL or a `json_t *` array
+ * @param key current key
+ * @param value a `struct Entry`
+ * @return #GNUNET_YES if the iteration should continue,
+ *         #GNUNET_NO otherwise.
+ */
+static int
+hashmap_free (void *cls,
+              const struct GNUNET_HashCode *key,
+              void *value)
+{
+  json_t *ja = cls;
+  struct Entry *entry = value;
+
+  (void) key;
+  if (NULL != ja)
+  {
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (
+        ja,
+        json_pack ("{s:s,s:o,s:o}",
+                   "order_id",
+                   entry->order_id,
+                   "deposit_value",
+                   TALER_JSON_from_amount (&entry->deposit_value),
+                   "deposit_fee",
+                   TALER_JSON_from_amount (&entry->deposit_fee))));
+  }
+  GNUNET_free (entry->order_id);
+  GNUNET_free (entry);
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle a timeout for the processing of the track transfer request.
+ *
+ * @param cls closure
+ */
+static void
+handle_transfer_timeout (void *cls)
+{
+  struct PostTransfersContext *ptc = cls;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Resuming POST /private/transfers with error after timeout\n");
+  ptc->timeout_task = NULL;
+  if (NULL != ptc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (ptc->fo);
+    ptc->fo = NULL;
+  }
+  if (NULL != ptc->wdh)
+  {
+    TALER_EXCHANGE_transfers_get_cancel (ptc->wdh);
+    ptc->wdh = NULL;
+  }
+  resume_transfer_with_error (ptc,
+                              MHD_HTTP_SERVICE_UNAVAILABLE,
+                              TALER_EC_POST_TRANSFERS_EXCHANGE_TIMEOUT,
+                              "exchange not reachable");
+}
+
+
+/**
+ * Manages a POST /private/transfers call. It calls the GET /transfers/$WTID
+ * offered by the exchange in order to obtain the set of transfers
+ * (of coins) associated with a given wire transfer.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
+                            struct MHD_Connection *connection,
+                            struct TMH_HandlerContext *hc)
+{
+  struct PostTransfersContext *ptc = hc->ctx;
+  enum GNUNET_DB_QueryStatus qs;
+
+  if (NULL == ptc)
+  {
+    ptc = GNUNET_new (struct PostTransfersContext);
+    ptc->connection = connection;
+    ptc->hc = hc;
+    hc->ctx = ptc;
+    hc->cc = &transfer_cleanup;
+  }
+
+queue:
+  if (0 != ptc->response_code)
+  {
+    MHD_RESULT ret;
+
+    /* We are *done* processing the request, just queue the response (!) */
+    if (UINT_MAX == ptc->response_code)
+    {
+      GNUNET_break (0);
+      return MHD_NO; /* hard error */
+    }
+    ret = MHD_queue_response (connection,
+                              ptc->response_code,
+                              ptc->response);
+    if (NULL != ptc->response)
+    {
+      MHD_destroy_response (ptc->response);
+      ptc->response = NULL;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Queueing response (%u) for POST /private/transfers (%s).\n",
+                (unsigned int) ptc->response_code,
+                ret ? "OK" : "FAILED");
+    return ret;
+  }
+
+  if ( (NULL != ptc->fo) ||
+       (NULL != ptc->wdh) )
+  {
+    /* likely old MHD version */
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Not sure why we are here, should be suspended\n");
+    return MHD_YES; /* still work in progress */
+  }
+
+  if (NULL == ptc->exchange_url)
+  {
+    /* First request, parse it! */
+    struct GNUNET_JSON_Specification spec[] = {
+      TALER_JSON_spec_amount ("credit_amount",
+                              &ptc->amount),
+      GNUNET_JSON_spec_fixed_auto ("wtid",
+                                   &ptc->wtid),
+      GNUNET_JSON_spec_string ("payto_uri",
+                               &ptc->payto_uri),
+      GNUNET_JSON_spec_string ("exchange_url",
+                               &ptc->exchange_url),
+      GNUNET_JSON_spec_end ()
+    };
+
+    {
+      enum GNUNET_GenericReturnValue res;
+
+      res = TALER_MHD_parse_json_data (connection,
+                                       hc->request_body,
+                                       spec);
+      if (GNUNET_OK != res)
+        return (GNUNET_NO == res)
+               ? MHD_YES
+               : MHD_NO;
+    }
+  }
+
+  /* Check if transfer data is in database! */
+  {
+    struct GNUNET_TIME_Absolute execution_time;
+    struct TALER_Amount total_amount;
+    struct TALER_Amount wire_fee;
+    bool verified;
+
+    TMH_db->preflight (TMH_db->cls);
+    qs = TMH_db->lookup_transfer (TMH_db->cls,
+                                  ptc->exchange_url,
+                                  &ptc->wtid,
+                                  &total_amount,
+                                  &wire_fee,
+                                  &execution_time,
+                                  &verified);
+    if (0 > qs)
+    {
+      /* Simple select queries should not cause serialization issues */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_POST_TRANSFERS_DB_LOOKUP_ERROR,
+                                         "Failed to query database about 
transfer details");
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      goto fetch;
+    if (! verified)
+    {
+      if (GNUNET_SYSERR ==
+          check_wire_fee (ptc,
+                          execution_time,
+                          &wire_fee))
+      {
+        GNUNET_assert (0 != ptc->response_code);
+        goto queue;
+      }
+
+      qs = TMH_db->lookup_transfer_details (TMH_db->cls,
+                                            ptc->exchange_url,
+                                            &ptc->wtid,
+                                            &verify_exchange_claim_cb,
+                                            ptc);
+      if (0 != ptc->response_code)
+        goto queue;
+      verified = true;
+      qs = TMH_db->set_transfer_status_to_verified (TMH_db->cls,
+                                                    ptc->exchange_url,
+                                                    &ptc->wtid);
+      GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+    }
+
+    /* Short version: we already verified, generate the summary response */
+    GNUNET_assert (verified);
+    {
+      struct GNUNET_CONTAINER_MultiHashMap *map;
+      json_t *deposit_sums;
+
+      map = GNUNET_CONTAINER_multihashmap_create (16,
+                                                  GNUNET_NO);
+      qs = TMH_db->lookup_transfer_summary (TMH_db->cls,
+                                            ptc->exchange_url,
+                                            &ptc->wtid,
+                                            &transfer_summary_cb,
+                                            map);
+      if (0 > qs)
+      {
+        /* Simple select queries should not cause serialization issues */
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+        /* Always report on hard error as well to enable diagnostics */
+        GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+        GNUNET_CONTAINER_multihashmap_iterate (map,
+                                               &hashmap_free,
+                                               NULL);
+        GNUNET_CONTAINER_multihashmap_destroy (map);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_POST_TRANSFERS_DB_LOOKUP_ERROR,
+                                           "Failed to query database about 
transfer details");
+      }
+
+      deposit_sums = json_array ();
+      GNUNET_assert (NULL != deposit_sums);
+      GNUNET_CONTAINER_multihashmap_iterate (map,
+                                             &hashmap_free,
+                                             deposit_sums);
+      GNUNET_CONTAINER_multihashmap_destroy (map);
+      return TALER_MHD_reply_json_pack (
+        connection,
+        MHD_HTTP_OK,
+        "{s:o,s:o,s:o,s:o}",
+        "total", TALER_JSON_from_amount (&total_amount),
+        "wire_fee", TALER_JSON_from_amount (&wire_fee),
+        "execution_time", GNUNET_JSON_from_time_abs (execution_time),
+        "deposit_sums", deposit_sums);
+    } /* end of 'verified == true' */
+  } /* end of 'transfer data in database' */
+
+  /* reply not in database, ensure the POST is in the database, and
+     start work to obtain the reply from the exchange */
+fetch:
+  qs = TMH_db->insert_transfer (TMH_db->cls,
+                                ptc->hc->instance->settings.id,
+                                ptc->exchange_url,
+                                &ptc->wtid,
+                                &ptc->amount,
+                                ptc->payto_uri,
+                                true /* confirmed! */);
+  if (0 > qs)
+  {
+    /* Simple select queries should not cause serialization issues */
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    /* Always report on hard error as well to enable diagnostics */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_POST_TRANSFERS_DB_STORE_ERROR,
+                                       "Fail to update database with transfer 
record");
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    uint64_t account_serial;
+
+    /* Either the record already exists (we should ignore this), or
+       the INSERT failed because we did not find the account based on
+       the given payto-URI and the instance. */
+    qs = TMH_db->lookup_account (TMH_db->cls,
+                                 ptc->hc->instance->settings.id,
+                                 ptc->payto_uri,
+                                 &account_serial);
+    if (0 >= qs)
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_POST_TRANSFERS_ACCOUNT_NOT_FOUND,
+                                         "Instance does not have this bank 
account");
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Suspending POST /private/transfers handling while working with 
exchange\n");
+  MHD_suspend_connection (connection);
+  GNUNET_CONTAINER_DLL_insert (ptc_head,
+                               ptc_tail,
+                               ptc);
+  ptc->fo = TMH_EXCHANGES_find_exchange (ptc->exchange_url,
+                                         NULL,
+                                         GNUNET_NO,
+                                         &process_transfer_with_exchange,
+                                         ptc);
+  ptc->timeout_task
+    = GNUNET_SCHEDULER_add_delayed (TRANSFER_TIMEOUT,
+                                    &handle_transfer_timeout,
+                                    ptc);
+  return MHD_YES;
+}
+
+
+/* end of taler-merchant-httpd_private-post-transfers.c */
diff --git a/src/backend/taler-merchant-httpd_proposal.h 
b/src/backend/taler-merchant-httpd_private-post-transfers.h
similarity index 52%
rename from src/backend/taler-merchant-httpd_proposal.h
rename to src/backend/taler-merchant-httpd_private-post-transfers.h
index 677fee0..676b27d 100644
--- a/src/backend/taler-merchant-httpd_proposal.h
+++ b/src/backend/taler-merchant-httpd_private-post-transfers.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014, 2015 INRIA
+  (C) 2014-2020 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,34 +14,38 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_propose.h
- * @brief headers for /contract handler
+ * @file backend/taler-merchant-httpd_private-post-transfers.h
+ * @brief headers for /track/transfer handler
+ * @author Christian Grothoff
  * @author Marcello Stanisci
  */
-#ifndef TALER_MERCHANT_HTTPD_CONTRACT_H
-#define TALER_MERCHANT_HTTPD_CONTRACT_H
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_TRANSFERS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_TRANSFERS_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
+
+/**
+ * We are shutting down, force resume of all POST /transfers requests.
+ */
+void
+TMH_force_post_transfers_resume (void);
+
+
 /**
- * Manage a GET /proposal request. Query the db and returns the
- * proposal's data related to the transaction id given as the URL's
- * parameter.
+ * Manages a POST /private/transfers call. It calls the GET /transfers/$WTID
+ * offered by the exchange in order to obtain the set of transfers
+ * (of coins) associated with a given wire transfer.
  *
  * @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
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_proposal_lookup (struct TMH_RequestHandler *rh,
+TMH_private_post_transfers (const struct TMH_RequestHandler *rh,
                             struct MHD_Connection *connection,
-                            void **connection_cls,
-                            const char *upload_data,
-                            size_t *upload_data_size,
-                            struct MerchantInstance *mi);
+                            struct TMH_HandlerContext *hc);
+
 
 #endif
diff --git a/src/backend/taler-merchant-httpd_proposal.c 
b/src/backend/taler-merchant-httpd_proposal.c
deleted file mode 100644
index 4720713..0000000
--- a/src/backend/taler-merchant-httpd_proposal.c
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014, 2015, 2016, 2018 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_proposal.c
- * @brief HTTP serving layer mainly intended to communicate
- * with the frontend
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_auditors.h"
-#include "taler-merchant-httpd_exchanges.h"
-
-
-/**
- * How often do we retry the simple INSERT database transaction?
- */
-#define MAX_RETRIES 3
-
-/**
- * Manage a GET /proposal request. Query the db and returns the
- * proposal's data related to the transaction id given as the URL's
- * parameter.
- *
- * Binds the proposal to a nonce.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_proposal_lookup (struct TMH_RequestHandler *rh,
-                            struct MHD_Connection *connection,
-                            void **connection_cls,
-                            const char *upload_data,
-                            size_t *upload_data_size,
-                            struct MerchantInstance *mi)
-{
-  const char *order_id;
-  const char *nonce;
-  enum GNUNET_DB_QueryStatus qs;
-  json_t *contract_terms;
-  struct GNUNET_CRYPTO_EddsaSignature merchant_sig;
-  const char *stored_nonce;
-
-  order_id = MHD_lookup_connection_value (connection,
-                                          MHD_GET_ARGUMENT_KIND,
-                                          "order_id");
-  if (NULL == order_id)
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MISSING,
-                                       "order_id");
-  nonce = MHD_lookup_connection_value (connection,
-                                       MHD_GET_ARGUMENT_KIND,
-                                       "nonce");
-  if (NULL == nonce)
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MISSING,
-                                       "nonce");
-  db->preflight (db->cls);
-  qs = db->find_contract_terms (db->cls,
-                                &contract_terms,
-                                order_id,
-                                &mi->pubkey);
-  if (0 > qs)
-  {
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_PROPOSAL_LOOKUP_DB_ERROR,
-                                       "An error occurred while retrieving 
proposal data from db");
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    struct GNUNET_TIME_Absolute timestamp;
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
-      GNUNET_JSON_spec_end ()
-    };
-    enum GNUNET_GenericReturnValue res;
-
-    db->preflight (db->cls);
-    qs = db->find_order (db->cls,
-                         &contract_terms,
-                         order_id,
-                         &mi->pubkey);
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-    {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_PROPOSAL_LOOKUP_NOT_FOUND,
-                                         "unknown order id");
-    }
-    GNUNET_assert (NULL != contract_terms);
-    json_object_set_new (contract_terms,
-                         "nonce",
-                         json_string (nonce));
-
-    /* extract fields we need to sign separately */
-    res = TALER_MHD_parse_json_data (connection,
-                                     contract_terms,
-                                     spec);
-    if (GNUNET_NO == res)
-    {
-      return MHD_YES;
-    }
-    if (GNUNET_SYSERR == res)
-    {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_PROPOSAL_ORDER_PARSE_ERROR,
-                                         "Impossible to parse the order");
-    }
-
-    for (unsigned int i = 0; i<MAX_RETRIES; i++)
-    {
-      db->preflight (db->cls);
-      qs = db->insert_contract_terms (db->cls,
-                                      order_id,
-                                      &mi->pubkey,
-                                      timestamp,
-                                      contract_terms);
-      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-        break;
-    }
-    if (0 > qs)
-    {
-      /* Special report if retries insufficient */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_PROPOSAL_STORE_DB_ERROR,
-                                         "db error: could not store this 
proposal's data into db");
-    }
-    // FIXME: now we can delete (merchant_pub, order_id) from the 
merchant_orders table
-  }
-
-  GNUNET_assert (NULL != contract_terms);
-
-  stored_nonce
-    = json_string_value (json_object_get (contract_terms,
-                                          "nonce"));
-
-  if (NULL == stored_nonce)
-  {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_PROPOSAL_ORDER_PARSE_ERROR,
-                                       "existing proposal has no nonce");
-  }
-
-  if (0 != strcmp (stored_nonce,
-                   nonce))
-  {
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PROPOSAL_LOOKUP_NOT_FOUND,
-                                       "mismatched nonce");
-  }
-
-
-  /* create proposal signature */
-  {
-    struct TALER_ProposalDataPS pdps = {
-      .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT),
-      .purpose.size = htonl (sizeof (pdps))
-    };
-
-    if (GNUNET_OK !=
-        TALER_JSON_hash (contract_terms,
-                         &pdps.hash))
-    {
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_INTERNAL_LOGIC_ERROR,
-                                         "Could not hash order");
-    }
-
-    GNUNET_CRYPTO_eddsa_sign (&mi->privkey.eddsa_priv,
-                              &pdps,
-                              &merchant_sig);
-  }
-  return TALER_MHD_reply_json_pack (connection,
-                                    MHD_HTTP_OK,
-                                    "{ s:o, s:o }",
-                                    "contract_terms",
-                                    contract_terms,
-                                    "sig",
-                                    GNUNET_JSON_from_data_auto (
-                                      &merchant_sig));
-}
-
-
-/* end of taler-merchant-httpd_proposal.c */
diff --git a/src/backend/taler-merchant-httpd_refund.h 
b/src/backend/taler-merchant-httpd_refund.h
deleted file mode 100644
index f0fb44d..0000000
--- a/src/backend/taler-merchant-httpd_refund.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014, 2015, 2016, 2017 INRIA
-
-  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_refund.c
- * @brief HTTP serving layer mainly intended to communicate with the frontend
- * @author Marcello Stanisci
- */
-
-#ifndef TALER_MERCHANT_HTTPD_REFUND_H
-#define TALER_MERCHANT_HTTPD_REFUND_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * Return refund situation about a contract.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
-                          struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi);
-
-
-/**
- * Force resuming all suspended refund lookups, needed during shutdown.
- */
-void
-MH_force_refund_resume (void);
-
-
-/**
- * Get the JSON representation of a refund.
- *
- * @param merchant_pub the merchant's public key
- * @param mi merchant instance
- * @param ret_ec where to store error code
- * @param ret_errmsg where to store error message
- * @return NULL on error, JSON array with refunds on success
- */
-json_t *
-TM_get_refund_json (const struct MerchantInstance *mi,
-                    const struct GNUNET_HashCode *h_contract_terms,
-                    enum TALER_ErrorCode *ret_ec,
-                    const char **ret_errmsg);
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_refund_increase.c 
b/src/backend/taler-merchant-httpd_refund_increase.c
deleted file mode 100644
index d9c9e9c..0000000
--- a/src/backend/taler-merchant-httpd_refund_increase.c
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014-2020 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_refund_increase.c
- * @brief Handle request to increase the refund for an order
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_refund.h"
-
-/**
- * How often do we retry the non-trivial refund INSERT database
- * transaction?
- */
-#define MAX_RETRIES 5
-
-
-/**
- * Information we keep for individual calls
- * to requests that parse JSON, but keep no other state.
- */
-struct TMH_JsonParseContext
-{
-
-  /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
-   */
-  void *json_parse_context;
-};
-
-
-/**
- * Make a taler://refund URI
- *
- * @param connection MHD connection to take host and path from
- * @param instance_id merchant's instance ID, must not be NULL
- * @param order_id order ID to show a refund for, must not be NULL
- * @returns the URI, must be freed with #GNUNET_free
- */
-static char *
-make_taler_refund_uri (struct MHD_Connection *connection,
-                       const char *instance_id,
-                       const char *order_id)
-{
-  const char *host;
-  const char *forwarded_host;
-  const char *uri_path;
-  const char *uri_instance_id;
-  const char *query;
-  char *result;
-
-  GNUNET_assert (NULL != instance_id);
-  GNUNET_assert (NULL != order_id);
-  host = MHD_lookup_connection_value (connection,
-                                      MHD_HEADER_KIND,
-                                      MHD_HTTP_HEADER_HOST);
-  forwarded_host = MHD_lookup_connection_value (connection,
-                                                MHD_HEADER_KIND,
-                                                "X-Forwarded-Host");
-  if (NULL != forwarded_host)
-    host = forwarded_host;
-  if (NULL == host)
-  {
-    /* Should never happen, at least the host header should be defined */
-    GNUNET_break (0);
-    return NULL;
-  }
-  uri_path = MHD_lookup_connection_value (connection,
-                                          MHD_HEADER_KIND,
-                                          "X-Forwarded-Prefix");
-  if (NULL == uri_path)
-    uri_path = "-";
-  if (0 == strcmp (instance_id,
-                   "default"))
-    uri_instance_id = "-";
-  else
-    uri_instance_id = instance_id;
-  if (GNUNET_YES == TALER_mhd_is_https (connection))
-    query = "";
-  else
-    query = "?insecure=1";
-  GNUNET_assert (0 < GNUNET_asprintf (&result,
-                                      "taler://refund/%s/%s/%s/%s%s",
-                                      host,
-                                      uri_path,
-                                      uri_instance_id,
-                                      order_id,
-                                      query));
-  return result;
-}
-
-
-/**
- * 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;
-
-  TALER_MHD_parse_post_cleanup_callback (jpc->json_parse_context);
-  GNUNET_free (jpc);
-}
-
-
-/**
- * Process a refund request.
- *
- * @param connection HTTP client connection
- * @param mi merchant instance doing the processing
- * @param refund amount to be refunded
- * @param order_id for which order is the refund
- * @param reason reason for the refund
- * @return MHD result code
- */
-static MHD_RESULT
-process_refund (struct MHD_Connection *connection,
-                struct MerchantInstance *mi,
-                const struct TALER_Amount *refund,
-                const char *order_id,
-                const char *reason)
-{
-  json_t *contract_terms;
-  enum GNUNET_DB_QueryStatus qs;
-  enum GNUNET_DB_QueryStatus qsx;
-  struct GNUNET_HashCode h_contract_terms;
-
-  db->preflight (db->cls);
-  /* Convert order id to h_contract_terms */
-  qs = db->find_contract_terms (db->cls,
-                                &contract_terms,
-                                order_id,
-                                &mi->pubkey);
-  if (0 > qs)
-  {
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_REFUND_LOOKUP_DB_ERROR,
-                                       "An error occurred while retrieving 
payment data from db");
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Unknown order id given: `%s'\n",
-                order_id);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_REFUND_ORDER_ID_UNKNOWN,
-                                       "order_id not found in database");
-  }
-
-  if (GNUNET_OK !=
-      TALER_JSON_hash (contract_terms,
-                       &h_contract_terms))
-  {
-    GNUNET_break (0);
-    json_decref (contract_terms);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_INTERNAL_LOGIC_ERROR,
-                                       "Could not hash contract terms");
-  }
-  json_decref (contract_terms);
-  for (unsigned int i = 0; i<MAX_RETRIES; i++)
-  {
-    if (GNUNET_OK !=
-        db->start (db->cls,
-                   "increase refund"))
-    {
-      GNUNET_break (0);
-      return GNUNET_DB_STATUS_HARD_ERROR;
-    }
-    qs = db->increase_refund_for_contract_NT (db->cls,
-                                              &h_contract_terms,
-                                              &mi->pubkey,
-                                              refund,
-                                              reason);
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "increase refund returned %d\n",
-                qs);
-    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
-    {
-      GNUNET_break (0);
-      db->rollback (db->cls);
-      break;
-    }
-    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-    {
-      db->rollback (db->cls);
-      continue;
-    }
-    /* Got one or more deposits */
-    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
-    {
-      db->rollback (db->cls);
-      break;
-    }
-    qsx = db->commit (db->cls);
-    if (GNUNET_DB_STATUS_HARD_ERROR == qsx)
-    {
-      GNUNET_break (0);
-      qs = qsx;
-      break;
-    }
-    if (GNUNET_DB_STATUS_SOFT_ERROR != qsx)
-      break;
-  }
-  if (0 > qs)
-  {
-    /* Special report if retries insufficient */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_REFUND_MERCHANT_DB_COMMIT_ERROR,
-                                       "Internal database error or refund 
amount too big");
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Refusing refund amount %s that is larger than original 
payment\n",
-                TALER_amount2s (refund));
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_CONFLICT,
-                                       TALER_EC_REFUND_INCONSISTENT_AMOUNT,
-                                       "Amount above payment");
-  }
-
-  /* Resume /public/poll-payments clients that may wait for this refund */
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Awakeing clients on %s waiting for refund of less than %s\n",
-              order_id,
-              TALER_amount2s (refund));
-
-  TMH_long_poll_resume (order_id,
-                        &mi->pubkey,
-                        refund);
-
-  {
-    MHD_RESULT ret;
-    char *taler_refund_uri;
-
-    taler_refund_uri = make_taler_refund_uri (connection,
-                                              mi->id,
-                                              order_id);
-    ret = TALER_MHD_reply_json_pack (
-      connection,
-      MHD_HTTP_OK,
-      "{s:o, s:s}",
-      "h_contract_terms",
-      GNUNET_JSON_from_data_auto (&h_contract_terms),
-      "taler_refund_uri",
-      taler_refund_uri);
-    GNUNET_free (taler_refund_uri);
-    return ret;
-  }
-}
-
-
-/**
- * Handle request for increasing the refund associated with
- * a contract.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_refund_increase (struct TMH_RequestHandler *rh,
-                            struct MHD_Connection *connection,
-                            void **connection_cls,
-                            const char *upload_data,
-                            size_t *upload_data_size,
-                            struct MerchantInstance *mi)
-{
-  enum GNUNET_GenericReturnValue res;
-  struct TMH_JsonParseContext *ctx;
-  struct TALER_Amount refund;
-  const char *order_id;
-  const char *reason;
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount ("refund", &refund),
-    GNUNET_JSON_spec_string ("order_id", &order_id),
-    GNUNET_JSON_spec_string ("reason", &reason),
-    GNUNET_JSON_spec_end ()
-  };
-  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 = TALER_MHD_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 = TALER_MHD_parse_json_data (connection,
-                                   root,
-                                   spec);
-  if (GNUNET_NO == res)
-  {
-    GNUNET_break_op (0);
-    json_decref (root);
-    return MHD_YES;
-  }
-  if (GNUNET_SYSERR == res)
-  {
-    GNUNET_break_op (0);
-    json_decref (root);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_JSON_INVALID,
-                                       "Request body does not match 
specification");
-  }
-  {
-    MHD_RESULT ret;
-
-    ret = process_refund (connection,
-                          mi,
-                          &refund,
-                          order_id,
-                          reason);
-    GNUNET_JSON_parse_free (spec);
-    json_decref (root);
-    return ret;
-  }
-}
-
-
-/* end of taler-merchant-httpd_refund_increase.c */
diff --git a/src/backend/taler-merchant-httpd_refund_lookup.c 
b/src/backend/taler-merchant-httpd_refund_lookup.c
deleted file mode 100644
index e86e4e4..0000000
--- a/src/backend/taler-merchant-httpd_refund_lookup.c
+++ /dev/null
@@ -1,654 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014-2020 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_refund_lookup.c
- * @brief refund handling logic
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_refund.h"
-
-/**
- * How often do we retry DB transactions on serialization failures?
- */
-#define MAX_RETRIES 5
-
-/**
- * Information we keep for each coin to be refunded.
- */
-struct CoinRefund
-{
-
-  /**
-   * Kept in a DLL.
-   */
-  struct CoinRefund *next;
-
-  /**
-   * Kept in a DLL.
-   */
-  struct CoinRefund *prev;
-
-  /**
-   * Request to connect to the target exchange.
-   */
-  struct TMH_EXCHANGES_FindOperation *fo;
-
-  /**
-   * Handle for the refund operation with the exchange.
-   */
-  struct TALER_EXCHANGE_RefundHandle *rh;
-
-  /**
-   * PRD this operation is part of.
-   */
-  struct ProcessRefundData *prd;
-
-  /**
-   * URL of the exchange for this @e coin_pub.
-   */
-  char *exchange_url;
-
-  /**
-   * Coin to refund.
-   */
-  struct TALER_CoinSpendPublicKeyP coin_pub;
-
-  /**
-   * Refund transaction ID to use.
-   */
-  uint64_t rtransaction_id;
-
-  /**
-   * Amount to refund.
-   */
-  struct TALER_Amount refund_amount;
-
-  /**
-   * Applicable refund transaction fee.
-   */
-  struct TALER_Amount refund_fee;
-
-  /**
-   * Public key of the exchange affirming the refund.
-   */
-  struct TALER_ExchangePublicKeyP exchange_pub;
-
-  /**
-   * Signature of the exchange affirming the refund.
-   */
-  struct TALER_ExchangeSignatureP exchange_sig;
-
-  /**
-   * HTTP status from the exchange, #MHD_HTTP_OK if
-   * @a exchange_pub and @a exchange_sig are valid.
-   */
-  unsigned int exchange_status;
-
-  /**
-   * HTTP error code from the exchange.
-   */
-  enum TALER_ErrorCode exchange_code;
-
-  /**
-   * Fully reply from the exchange, only possibly set if
-   * we got a JSON reply and a non-#MHD_HTTP_OK error code
-   */
-  json_t *exchange_reply;
-
-};
-
-
-/**
- * Closure for #process_refunds_cb.
- */
-struct ProcessRefundData
-{
-  /**
-   * Must be first for #handle_mhd_completion_callback() cleanup
-   * logic to work.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Hashed version of contract terms.
-   */
-  struct GNUNET_HashCode h_contract_terms;
-
-  /**
-   * DLL of (suspended) requests.
-   */
-  struct ProcessRefundData *next;
-
-  /**
-   * DLL of (suspended) requests.
-   */
-  struct ProcessRefundData *prev;
-
-  /**
-   * Head of DLL of coin refunds for this request.
-   */
-  struct CoinRefund *cr_head;
-
-  /**
-   * Tail of DLL of coin refunds for this request.
-   */
-  struct CoinRefund *cr_tail;
-
-  /**
-   * Both public and private key are needed by the callback
-   */
-  const struct MerchantInstance *merchant;
-
-  /**
-   * Connection we are handling.
-   */
-  struct MHD_Connection *connection;
-
-  /**
-   * Did we suspend @a connection?
-   */
-  int suspended;
-
-  /**
-   * Return code: #TALER_EC_NONE if successful.
-   */
-  enum TALER_ErrorCode ec;
-};
-
-
-/**
- * HEad of DLL of (suspended) requests.
- */
-static struct ProcessRefundData *prd_head;
-
-/**
- * Tail of DLL of (suspended) requests.
- */
-static struct ProcessRefundData *prd_tail;
-
-
-/**
- * Clean up memory in @a cls, the connection was closed.
- *
- * @param cls a `struct ProcessRefundData` to clean up.
- */
-static void
-cleanup_prd (struct TM_HandlerContext *cls)
-{
-  struct ProcessRefundData *prd = (struct ProcessRefundData *) cls;
-  struct CoinRefund *cr;
-
-  while (NULL != (cr = prd->cr_head))
-  {
-    GNUNET_CONTAINER_DLL_remove (prd->cr_head,
-                                 prd->cr_tail,
-                                 cr);
-    if (NULL != cr->fo)
-    {
-      TMH_EXCHANGES_find_exchange_cancel (cr->fo);
-      cr->fo = NULL;
-    }
-    if (NULL != cr->rh)
-    {
-      TALER_EXCHANGE_refund_cancel (cr->rh);
-      cr->rh = NULL;
-    }
-    if (NULL != cr->exchange_reply)
-    {
-      json_decref (cr->exchange_reply);
-      cr->exchange_reply = NULL;
-    }
-    GNUNET_free (cr->exchange_url);
-    GNUNET_free (cr);
-  }
-  GNUNET_free (prd);
-}
-
-
-/**
- * Check if @a prd has sub-activities still pending.
- *
- * @param prd request to check
- * @return #GNUNET_YES if activities are still pending
- */
-static int
-prd_pending (struct ProcessRefundData *prd)
-{
-  int pending = GNUNET_NO;
-
-  for (struct CoinRefund *cr = prd->cr_head;
-       NULL != cr;
-       cr = cr->next)
-  {
-    if ( (NULL != cr->fo) ||
-         (NULL != cr->rh) )
-    {
-      pending = GNUNET_YES;
-      break;
-    }
-  }
-  return pending;
-}
-
-
-/**
- * Check if @a prd is ready to be resumed, and if so, do it.
- *
- * @param prd refund request to be possibly ready
- */
-static void
-check_resume_prd (struct ProcessRefundData *prd)
-{
-  if (prd_pending (prd))
-    return;
-  GNUNET_CONTAINER_DLL_remove (prd_head,
-                               prd_tail,
-                               prd);
-  GNUNET_assert (prd->suspended);
-  prd->suspended = GNUNET_NO;
-  MHD_resume_connection (prd->connection);
-  TMH_trigger_daemon ();
-}
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * refund request to an exchange.
- *
- * @param cls a `struct CoinRefund`
- * @param hr HTTP response data
- * @param exchange_pub exchange key used to sign refund confirmation
- * @param exchange_sig exchange's signature over refund
- */
-static void
-refund_cb (void *cls,
-           const struct TALER_EXCHANGE_HttpResponse *hr,
-           const struct TALER_ExchangePublicKeyP *exchange_pub,
-           const struct TALER_ExchangeSignatureP *exchange_sig)
-{
-  struct CoinRefund *cr = cls;
-
-  cr->rh = NULL;
-  cr->exchange_status = hr->http_status;
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Exchange refund status for coin %s is %u\n",
-              TALER_B2S (&cr->coin_pub),
-              hr->http_status);
-  if (MHD_HTTP_OK != hr->http_status)
-  {
-    cr->exchange_code = hr->ec;
-    cr->exchange_reply = json_incref ((json_t*) hr->reply);
-  }
-  else
-  {
-    enum GNUNET_DB_QueryStatus qs;
-
-    cr->exchange_pub = *exchange_pub;
-    cr->exchange_sig = *exchange_sig;
-    qs = db->put_refund_proof (db->cls,
-                               &cr->prd->merchant->pubkey,
-                               &cr->prd->h_contract_terms,
-                               &cr->coin_pub,
-                               cr->rtransaction_id,
-                               exchange_pub,
-                               exchange_sig);
-    if (0 >= qs)
-    {
-      /* generally, this is relatively harmless for the merchant, but let's at
-         least log this. */
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Failed to persist exchange response to /refund in database: 
%d\n",
-                  qs);
-    }
-  }
-  check_resume_prd (cr->prd);
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.
- *
- * @param cls a `struct CoinRefund *`
- * @param hr HTTP response details
- * @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,
-                   const struct TALER_EXCHANGE_HttpResponse *hr,
-                   struct TALER_EXCHANGE_Handle *eh,
-                   const struct TALER_Amount *wire_fee,
-                   int exchange_trusted)
-{
-  struct CoinRefund *cr = cls;
-
-  cr->fo = NULL;
-  if (TALER_EC_NONE == hr->ec)
-  {
-    cr->rh = TALER_EXCHANGE_refund (eh,
-                                    &cr->refund_amount,
-                                    &cr->refund_fee,
-                                    &cr->prd->h_contract_terms,
-                                    &cr->coin_pub,
-                                    cr->rtransaction_id,
-                                    &cr->prd->merchant->privkey,
-                                    &refund_cb,
-                                    cr);
-    return;
-  }
-  cr->exchange_status = hr->http_status;
-  cr->exchange_code = hr->ec;
-  cr->exchange_reply = json_incref ((json_t*) hr->reply);
-  check_resume_prd (cr->prd);
-}
-
-
-/**
- * Function called with information about a refund.
- * It is responsible for packing up the data to return.
- *
- * @param cls closure
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
- */
-static void
-process_refunds_cb (void *cls,
-                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                    const char *exchange_url,
-                    uint64_t rtransaction_id,
-                    const char *reason,
-                    const struct TALER_Amount *refund_amount,
-                    const struct TALER_Amount *refund_fee)
-{
-  struct ProcessRefundData *prd = cls;
-  struct CoinRefund *cr;
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Found refund of %s for coin %s with reason `%s' in database\n",
-              TALER_B2S (coin_pub),
-              TALER_amount2s (refund_amount),
-              reason);
-  cr = GNUNET_new (struct CoinRefund);
-  cr->exchange_url = GNUNET_strdup (exchange_url);
-  cr->prd = prd;
-  cr->coin_pub = *coin_pub;
-  cr->rtransaction_id = rtransaction_id;
-  cr->refund_amount = *refund_amount;
-  cr->refund_fee = *refund_fee;
-  GNUNET_CONTAINER_DLL_insert (prd->cr_head,
-                               prd->cr_tail,
-                               cr);
-}
-
-
-/**
- * Force resuming all suspended refund lookups, needed during shutdown.
- */
-void
-MH_force_refund_resume (void)
-{
-  struct ProcessRefundData *prd;
-
-  while (NULL != (prd = prd_head))
-  {
-    GNUNET_CONTAINER_DLL_remove (prd_head,
-                                 prd_tail,
-                                 prd);
-    GNUNET_assert (prd->suspended);
-    prd->suspended = GNUNET_NO;
-    MHD_resume_connection (prd->connection);
-  }
-}
-
-
-/**
- * Return refund situation about a contract.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
-                          struct MHD_Connection *connection,
-                          void **connection_cls,
-                          const char *upload_data,
-                          size_t *upload_data_size,
-                          struct MerchantInstance *mi)
-{
-  struct ProcessRefundData *prd;
-  const char *order_id;
-  json_t *contract_terms;
-  enum GNUNET_DB_QueryStatus qs;
-
-  prd = *connection_cls;
-  if (NULL == prd)
-  {
-    order_id = MHD_lookup_connection_value (connection,
-                                            MHD_GET_ARGUMENT_KIND,
-                                            "order_id");
-    if (NULL == order_id)
-    {
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "order_id");
-    }
-
-    /* Convert order id to h_contract_terms */
-    contract_terms = NULL;
-    db->preflight (db->cls);
-    qs = db->find_contract_terms (db->cls,
-                                  &contract_terms,
-                                  order_id,
-                                  &mi->pubkey);
-    if (0 > qs)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_REFUND_LOOKUP_DB_ERROR,
-                                         "database error looking up order_id 
from merchant_contract_terms table");
-    }
-
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Unknown order id given: `%s'\n",
-                  order_id);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         TALER_EC_REFUND_ORDER_ID_UNKNOWN,
-                                         "order_id not found in database");
-    }
-
-    prd = GNUNET_new (struct ProcessRefundData);
-    if (GNUNET_OK !=
-        TALER_JSON_hash (contract_terms,
-                         &prd->h_contract_terms))
-    {
-      GNUNET_break (0);
-      json_decref (contract_terms);
-      GNUNET_free (prd);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_INTERNAL_LOGIC_ERROR,
-                                         "Could not hash contract terms");
-    }
-    json_decref (contract_terms);
-    prd->hc.cc = &cleanup_prd;
-    prd->merchant = mi;
-    prd->ec = TALER_EC_NONE;
-    prd->connection = connection;
-    *connection_cls = prd;
-
-    for (unsigned int i = 0; i<MAX_RETRIES; i++)
-    {
-      qs = db->get_refunds_from_contract_terms_hash (db->cls,
-                                                     &mi->pubkey,
-                                                     &prd->h_contract_terms,
-                                                     &process_refunds_cb,
-                                                     prd);
-      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-        break;
-    }
-    if (0 > qs)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Database hard error on refunds_from_contract_terms_hash 
lookup: %s\n",
-                  GNUNET_h2s (&prd->h_contract_terms));
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_REFUND_LOOKUP_DB_ERROR,
-                                         "Failed to lookup refunds for 
contract");
-    }
-
-    /* Now launch exchange interactions, unless we already have the
-       response in the database! */
-    for (struct CoinRefund *cr = prd->cr_head;
-         NULL != cr;
-         cr = cr->next)
-    {
-      enum GNUNET_DB_QueryStatus qs;
-
-      qs = db->get_refund_proof (db->cls,
-                                 &cr->prd->merchant->pubkey,
-                                 &cr->prd->h_contract_terms,
-                                 &cr->coin_pub,
-                                 cr->rtransaction_id,
-                                 &cr->exchange_pub,
-                                 &cr->exchange_sig);
-      if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
-      {
-        /* We need to talk to the exchange */
-        cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
-                                              NULL,
-                                              GNUNET_NO,
-                                              &exchange_found_cb,
-                                              cr);
-      }
-    }
-  }
-
-  /* Check if there are still exchange operations pending */
-  if (GNUNET_YES == prd_pending (prd))
-  {
-    if (! prd->suspended)
-    {
-      prd->suspended = GNUNET_YES;
-      MHD_suspend_connection (connection);
-      GNUNET_CONTAINER_DLL_insert (prd_head,
-                                   prd_tail,
-                                   prd);
-    }
-    return MHD_YES;   /* we're still talking to the exchange */
-  }
-
-  /* All operations done, build final response */
-  if (NULL == prd->cr_head)
-  {
-    /* There ARE no refunds scheduled, bitch */
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_REFUND_LOOKUP_NO_REFUND,
-                                       "This contract is not currently 
eligible for refunds");
-  }
-
-  {
-    json_t *ra;
-
-    ra = json_array ();
-    GNUNET_assert (NULL != ra);
-    for (struct CoinRefund *cr = prd->cr_head;
-         NULL != cr;
-         cr = cr->next)
-    {
-      GNUNET_assert (
-        0 ==
-        json_array_append_new (
-          ra,
-          (MHD_HTTP_OK != cr->exchange_status)
-          ? json_pack ((NULL != cr->exchange_reply)
-                       ? "{s:o,s:o,s:o,s:I,s:I,s:I,s:O}"
-                       : "{s:o,s:o,s:o,s:I,s:I:s:I}",
-                       "coin_pub",
-                       GNUNET_JSON_from_data_auto (&cr->coin_pub),
-                       "refund_amount",
-                       TALER_JSON_from_amount (&cr->refund_amount),
-                       "refund_fee",
-                       TALER_JSON_from_amount (&cr->refund_fee),
-                       "exchange_http_status",
-                       (json_int_t) cr->exchange_status,
-                       "rtransaction_id",
-                       (json_int_t) cr->rtransaction_id,
-                       "exchange_code",
-                       (json_int_t) cr->exchange_code,
-                       "exchange_reply",
-                       cr->exchange_reply)
-          : json_pack ("{s:o,s:o,s:o,s:I,s:I,s:o,s:o}",
-                       "coin_pub",
-                       GNUNET_JSON_from_data_auto (&cr->coin_pub),
-                       "refund_amount",
-                       TALER_JSON_from_amount (&cr->refund_amount),
-                       "refund_fee",
-                       TALER_JSON_from_amount (&cr->refund_fee),
-                       "exchange_http_status",
-                       (json_int_t) cr->exchange_status,
-                       "rtransaction_id",
-                       (json_int_t) cr->rtransaction_id,
-                       "exchange_pub",
-                       GNUNET_JSON_from_data_auto (&cr->exchange_pub),
-                       "exchange_sig",
-                       GNUNET_JSON_from_data_auto (&cr->exchange_sig)
-                       )));
-    }
-    return TALER_MHD_reply_json_pack (
-      connection,
-      MHD_HTTP_OK,
-      "{s:o, s:o, s:o}",
-      "refunds",
-      ra,
-      "merchant_pub",
-      GNUNET_JSON_from_data_auto (&mi->pubkey),
-      "h_contract_terms",
-      GNUNET_JSON_from_data_auto (&prd->h_contract_terms));
-  }
-}
-
-
-/* end of taler-merchant-httpd_refund_lookup.c */
diff --git a/src/backend/taler-merchant-httpd_reserves.c 
b/src/backend/taler-merchant-httpd_reserves.c
new file mode 100644
index 0000000..172b6f9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_reserves.c
@@ -0,0 +1,359 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_reserves.c
+ * @brief logic for initially tracking a reserve's status
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_reserves.h"
+
+
+/**
+ * Our representation of a reserve that we are (still) checking the status of.
+ */
+struct Reserve
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct Reserve *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct Reserve *prev;
+
+  /**
+   * Reserve's public key.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Amount the merchant expects to see in the reserve initially.
+   * We log a warning if there is a missmatch.
+   */
+  struct TALER_Amount expected_amount;
+
+  /**
+   * URL of the exchange hosting this reserve.
+   */
+  char *exchange_url;
+
+  /**
+   * Instance this reserve belongs with.
+   */
+  char *instance_id;
+
+  /**
+   * Active find operation for this reserve.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Task scheduled waiting for a timeout for this reserve.
+   */
+  struct GNUNET_SCHEDULER_Task *tt;
+
+  /**
+   * Get operation with the exchange.
+   */
+  struct TALER_EXCHANGE_ReservesGetHandle *gh;
+
+  /**
+   * How long do we wait before trying this reserve again?
+   */
+  struct GNUNET_TIME_Relative delay;
+
+};
+
+
+/**
+ * Head of DLL of pending reserves.
+ */
+static struct Reserve *reserves_head;
+
+/**
+ * Tail of DLL of pending reserves.
+ */
+static struct Reserve *reserves_tail;
+
+
+/**
+ * Function called to probe a reserve now.
+ *
+ * @param cls a `struct Reserve` to query
+ */
+static void
+try_now (void *cls);
+
+
+/**
+ * Free reserve data structure.
+ *
+ * @param r reserve to free
+ */
+static void
+free_reserve (struct Reserve *r)
+{
+  GNUNET_CONTAINER_DLL_remove (reserves_head,
+                               reserves_tail,
+                               r);
+  if (NULL != r->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (r->fo);
+    r->fo = NULL;
+  }
+  if (NULL != r->gh)
+  {
+    TALER_EXCHANGE_reserves_get_cancel (r->gh);
+    r->gh = NULL;
+  }
+  if (NULL != r->tt)
+  {
+    GNUNET_SCHEDULER_cancel (r->tt);
+    r->tt = NULL;
+  }
+  GNUNET_free (r->exchange_url);
+  GNUNET_free (r->instance_id);
+  GNUNET_free (r);
+}
+
+
+/**
+ * Schedule a job to probe a reserve later again.
+ *
+ * @param r reserve to try again later
+ */
+static void
+try_later (struct Reserve *r)
+{
+  r->delay = GNUNET_TIME_STD_BACKOFF (r->delay);
+  r->tt = GNUNET_SCHEDULER_add_delayed (r->delay,
+                                        &try_now,
+                                        r);
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve status request to a exchange.
+ *
+ * @param cls closure with a `struct Reserve *`
+ * @param hr HTTP response data
+ * @param balance current balance in the reserve, NULL on error
+ * @param history_length number of entries in the transaction history, 0 on 
error
+ * @param history detailed transaction history, NULL on error
+ */
+static void
+reserve_cb (void *cls,
+            const struct TALER_EXCHANGE_HttpResponse *hr,
+            const struct TALER_Amount *balance,
+            unsigned int history_length,
+            const struct TALER_EXCHANGE_ReserveHistory *history)
+{
+  struct Reserve *r = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  r->gh = NULL;
+  if ( (NULL == hr) ||
+       (MHD_HTTP_OK != hr->http_status) )
+  {
+    try_later (r);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_amount_cmp_currency (&r->expected_amount,
+                                 balance))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Reserve currency disagreement: exchange `%s' has %s, expected 
%s\n",
+                r->exchange_url,
+                balance->currency,
+                r->expected_amount.currency);
+    free_reserve (r);
+    return;
+  }
+  if (0 !=
+      TALER_amount_cmp (&r->expected_amount,
+                        balance))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Reserve initial balance disagreement: exchange `%s' received 
`%s'\n",
+                r->exchange_url,
+                TALER_amount2s (balance));
+  }
+  qs = TMH_db->activate_reserve (TMH_db->cls,
+                                 r->instance_id,
+                                 &r->reserve_pub,
+                                 balance);
+  if (qs <= 0)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to commit reserve activation to database (%d)\n",
+                (int) qs);
+  }
+  free_reserve (r);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+find_cb (void *cls,
+         const struct TALER_EXCHANGE_HttpResponse *hr,
+         struct TALER_EXCHANGE_Handle *eh,
+         const char *payto_uri,
+         const struct TALER_Amount *wire_fee,
+         bool exchange_trusted)
+{
+  struct Reserve *r = cls;
+
+  r->fo = NULL;
+  if (NULL == eh)
+  {
+    try_later (r);
+    return;
+  }
+  r->gh = TALER_EXCHANGE_reserves_get (eh,
+                                       &r->reserve_pub,
+                                       &reserve_cb,
+                                       r);
+  if (NULL == r->gh)
+  {
+    try_later (r);
+    return;
+  }
+}
+
+
+/**
+ * Function called to probe a reserve now.
+ *
+ * @param cls a `struct Reserve` to query
+ */
+static void
+try_now (void *cls)
+{
+  struct Reserve *r = cls;
+
+  r->tt = NULL;
+  r->fo = TMH_EXCHANGES_find_exchange (r->exchange_url,
+                                       NULL,
+                                       GNUNET_NO,
+                                       &find_cb,
+                                       r);
+  if (NULL == r->fo)
+  {
+    try_later (r);
+    return;
+  }
+}
+
+
+/**
+ * Function called with information about a reserve that we need
+ * to check the status from at the exchange to see if/when it has
+ * been filled (and with what amount).
+ *
+ * @param cls closure, NULL
+ * @param instance_id for which instance is this reserve
+ * @param exchange_url base URL of the exchange at which the reserve lives
+ * @param reserve_pub public key of the reserve
+ * @param expected_amount how much do we expect to see in the reserve
+ */
+static void
+add_reserve (void *cls,
+             const char *instance_id,
+             const char *exchange_url,
+             const struct TALER_ReservePublicKeyP *reserve_pub,
+             const struct TALER_Amount *expected_amount)
+{
+  struct Reserve *r;
+
+  (void) cls;
+  r = GNUNET_new (struct Reserve);
+  r->exchange_url = GNUNET_strdup (exchange_url);
+  r->instance_id = GNUNET_strdup (instance_id);
+  r->reserve_pub = *reserve_pub;
+  r->expected_amount = *expected_amount;
+  GNUNET_CONTAINER_DLL_insert (reserves_head,
+                               reserves_tail,
+                               r);
+  try_now (r);
+}
+
+
+/**
+ * Load information about reserves and start querying reserve status.
+ * Must be called after the database is available.
+ */
+void
+TMH_RESERVES_init (void)
+{
+  TMH_db->lookup_pending_reserves (TMH_db->cls,
+                                   &add_reserve,
+                                   NULL);
+}
+
+
+/**
+ * Add a reserve to the list of reserves to check.
+ *
+ * @param instance_id which instance is the reserve for
+ * @param exchange_url URL of the exchange with the reserve
+ * @param reserve_pub public key of the reserve to check
+ * @param expected_amount amount the merchant expects to see initially in the 
reserve
+ */
+void
+TMH_RESERVES_check (const char *instance_id,
+                    const char *exchange_url,
+                    const struct TALER_ReservePublicKeyP *reserve_pub,
+                    const struct TALER_Amount *expected_amount)
+{
+  add_reserve (NULL,
+               instance_id,
+               exchange_url,
+               reserve_pub,
+               expected_amount);
+}
+
+
+/**
+ * Stop checking reserve status.
+ */
+void
+TMH_RESERVES_done (void)
+{
+  while (NULL != reserves_head)
+    free_reserve (reserves_head);
+}
+
+
+/* end of taler-merchant-httpd_reserves.c */
diff --git a/src/backend/taler-merchant-httpd_reserves.h 
b/src/backend/taler-merchant-httpd_reserves.h
new file mode 100644
index 0000000..e7cab1b
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_reserves.h
@@ -0,0 +1,61 @@
+/*
+  This file is part of TALER
+  (C) 2020 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_reserves.h
+ * @brief logic for initially tracking a reserve's status
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_RESERVES_H
+#define TALER_MERCHANT_HTTPD_RESERVES_H
+
+#include <jansson.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_util.h>
+#include <taler/taler_exchange_service.h>
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Load information about reserves and start querying reserve status.
+ * Must be called after the database is available.
+ */
+void
+TMH_RESERVES_init (void);
+
+
+/**
+ * Add a reserve to the list of reserves to check.
+ *
+ * @param instance_id which instance is the reserve for
+ * @param exchange_url URL of the exchange with the reserve
+ * @param reserve_pub public key of the reserve to check
+ * @param expected_amount amount the merchant expects to see initially in the 
reserve
+ */
+void
+TMH_RESERVES_check (const char *instance_id,
+                    const char *exchange_url,
+                    const struct TALER_ReservePublicKeyP *reserve_pub,
+                    const struct TALER_Amount *expected_amount);
+
+
+/**
+ * Stop checking reserve status.
+ */
+void
+TMH_RESERVES_done (void);
+
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_tip-authorize.c 
b/src/backend/taler-merchant-httpd_tip-authorize.c
deleted file mode 100644
index 569cf0a..0000000
--- a/src/backend/taler-merchant-httpd_tip-authorize.c
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
-  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_util.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_tip-authorize.h"
-#include "taler-merchant-httpd_tip-reserve-helper.h"
-
-
-struct TipAuthContext
-{
-  /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
-   */
-  void *json_parse_context;
-
-  /**
-   * Justification to use.
-   */
-  const char *justification;
-
-  /**
-   * JSON request received.
-   */
-  json_t *root;
-
-  /**
-   * Context for checking the tipping reserve's status.
-   */
-  struct TMH_CheckTipReserve ctr;
-
-  /**
-   * Tip amount requested.
-   */
-  struct TALER_Amount amount;
-
-  /**
-   * Flag set to #GNUNET_YES when we have tried /reserve/status of the
-   * tipping reserve already.
-   */
-  int checked_status;
-
-  /**
-   * Flag set to #GNUNET_YES when we have parsed the incoming JSON already.
-   */
-  int parsed_json;
-
-};
-
-
-/**
- * Custom cleanup routine for a `struct TipAuthContext`.
- *
- * @param hc the `struct TMH_JsonParseContext` to clean up.
- */
-static void
-cleanup_tac (struct TM_HandlerContext *hc)
-{
-  struct TipAuthContext *tac = (struct TipAuthContext *) hc;
-
-  if (NULL != tac->root)
-  {
-    json_decref (tac->root);
-    tac->root = NULL;
-  }
-  TMH_check_tip_reserve_cleanup (&tac->ctr);
-  TALER_MHD_parse_post_cleanup_callback (tac->json_parse_context);
-  GNUNET_free (tac);
-}
-
-
-/**
- * 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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-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)
-{
-  struct TipAuthContext *tac;
-  enum GNUNET_GenericReturnValue res;
-  enum TALER_ErrorCode ec;
-  struct GNUNET_TIME_Absolute expiration;
-  struct GNUNET_HashCode tip_id;
-  json_t *extra;
-
-  if (NULL == *connection_cls)
-  {
-    tac = GNUNET_new (struct TipAuthContext);
-    tac->hc.cc = &cleanup_tac;
-    tac->ctr.connection = connection;
-    *connection_cls = tac;
-  }
-  else
-  {
-    tac = *connection_cls;
-  }
-  if (NULL != tac->ctr.response)
-  {
-    MHD_RESULT ret;
-
-    ret = MHD_queue_response (connection,
-                              tac->ctr.response_code,
-                              tac->ctr.response);
-    MHD_destroy_response (tac->ctr.response);
-    tac->ctr.response = NULL;
-    return ret;
-  }
-  if (GNUNET_NO == tac->parsed_json)
-  {
-    struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_amount ("amount", &tac->amount),
-      GNUNET_JSON_spec_string ("justification", &tac->justification),
-      GNUNET_JSON_spec_end ()
-    };
-
-    res = TALER_MHD_parse_post_json (connection,
-                                     &tac->json_parse_context,
-                                     upload_data,
-                                     upload_data_size,
-                                     &tac->root);
-    if (GNUNET_SYSERR == res)
-      return MHD_NO;
-    /* the POST's body has to be further fetched */
-    if ( (GNUNET_NO == res) ||
-         (NULL == tac->root) )
-      return MHD_YES;
-
-    res = TALER_MHD_parse_json_data (connection,
-                                     tac->root,
-                                     spec);
-    if (GNUNET_YES != res)
-    {
-      GNUNET_break_op (0);
-      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
-    }
-    tac->parsed_json = GNUNET_YES;
-  }
-
-  if (NULL == mi->tip_exchange)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Instance `%s' not configured for tipping\n",
-                mi->id);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_PRECONDITION_FAILED,
-                                       
TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP,
-                                       "exchange for tipping not configured 
for the instance");
-  }
-  tac->ctr.reserve_priv = mi->tip_reserve;
-  extra = json_object_get (tac->root, "extra");
-  if (NULL == extra)
-    extra = json_object ();
-  else
-    json_incref (extra);
-
-
-  db->preflight (db->cls);
-  ec = db->authorize_tip_TR (db->cls,
-                             tac->justification,
-                             extra,
-                             &tac->amount,
-                             &mi->tip_reserve,
-                             mi->tip_exchange,
-                             &expiration,
-                             &tip_id);
-  json_decref (extra);
-  /* If we have insufficient funds according to OUR database,
-     check with exchange to see if the reserve has been topped up
-     in the meantime (or if tips were not withdrawn yet). */
-  if ( (TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS == ec) &&
-       (GNUNET_NO == tac->checked_status) )
-  {
-    tac->checked_status = GNUNET_YES;
-    tac->ctr.none_authorized = GNUNET_YES;
-    TMH_check_tip_reserve (&tac->ctr,
-                           mi->tip_exchange);
-    return MHD_YES;
-  }
-
-  /* handle irrecoverable errors */
-  if (TALER_EC_NONE != ec)
-  {
-    unsigned int rc;
-    const char *msg;
-
-    switch (ec)
-    {
-    case TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS:
-      rc = MHD_HTTP_PRECONDITION_FAILED;
-      msg = "Failed to approve tip: merchant has insufficient tipping funds";
-      break;
-    case TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED:
-      msg = "Failed to approve tip: merchant's tipping reserve expired";
-      rc = MHD_HTTP_PRECONDITION_FAILED;
-      break;
-    case TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN:
-      msg = "Failed to approve tip: merchant's tipping reserve does not exist";
-      rc = MHD_HTTP_SERVICE_UNAVAILABLE;
-      break;
-    default:
-      rc = MHD_HTTP_INTERNAL_SERVER_ERROR;
-      msg = "Failed to approve tip: internal server error";
-      break;
-    }
-
-    return TALER_MHD_reply_with_error (connection,
-                                       rc,
-                                       ec,
-                                       msg);
-  }
-
-  /* generate success response */
-  {
-    char *taler_tip_uri;
-    const char *host;
-    const char *forwarded_host;
-    const char *uri_path;
-    const char *uri_instance_id;
-    struct GNUNET_CRYPTO_HashAsciiEncoded hash_enc;
-
-    host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Host");
-    forwarded_host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
-                                                  "X-Forwarded-Host");
-
-    uri_path = MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
-                                            "X-Forwarded-Prefix");
-    if (NULL == uri_path)
-      uri_path = "-";
-
-    if (NULL != forwarded_host)
-      host = forwarded_host;
-
-    if (NULL == host)
-    {
-      /* Should never happen, at last the host header should be defined */
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_INTERNAL_INVARIANT_FAILURE,
-                                         "unable to identify backend host");
-    }
-
-    if (0 == strcmp (mi->id, "default"))
-      uri_instance_id = "-";
-    else
-      uri_instance_id = mi->id;
-
-    GNUNET_CRYPTO_hash_to_enc (&tip_id, &hash_enc);
-
-    GNUNET_assert (0 < GNUNET_asprintf (&taler_tip_uri,
-                                        "taler://tip/%s/%s/%s/%s",
-                                        host,
-                                        uri_path,
-                                        uri_instance_id,
-                                        hash_enc.encoding));
-
-    return TALER_MHD_reply_json_pack (connection,
-                                      MHD_HTTP_OK,
-                                      "{s:s, s:s}",
-                                      "taler_tip_uri", taler_tip_uri,
-                                      "tip_id", hash_enc.encoding);
-  }
-}
-
-
-/* end of taler-merchant-httpd_tip-authorize.c */
diff --git a/src/backend/taler-merchant-httpd_tip-pickup.c 
b/src/backend/taler-merchant-httpd_tip-pickup.c
deleted file mode 100644
index 51dd812..0000000
--- a/src/backend/taler-merchant-httpd_tip-pickup.c
+++ /dev/null
@@ -1,771 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2017-2020 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_exchanges.h"
-#include "taler-merchant-httpd_tip-pickup.h"
-
-
-/**
- * Information we keep per tip pickup request.
- */
-struct PickupContext;
-
-
-/**
- * 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
-{
-
-  /**
-   * Hash of the denomination public key requested for this planchet.
-   */
-  struct GNUNET_HashCode h_denom_pub;
-
-  /**
-   * Pickup context this planchet belongs to.
-   */
-  struct PickupContext *pc;
-
-  /**
-   * Handle to withdraw operation with the exchange.
-   */
-  struct TALER_EXCHANGE_Withdraw2Handle *wh;
-
-  /**
-   * Blind signature to return, or NULL if not available.
-   */
-  json_t *blind_sig;
-
-  /**
-   * 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 per tip pickup request.
- */
-struct PickupContext
-{
-
-  /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
-   */
-  void *json_parse_context;
-
-  /**
-   * Kept in a DLL while suspended.
-   */
-  struct PickupContext *next;
-
-  /**
-   * Kept in a DLL while suspended.
-   */
-  struct PickupContext *prev;
-
-  /**
-   * URL of the exchange this tip uses.
-   */
-  char *exchange_url;
-
-  /**
-   * Operation we run to find the exchange (and get its /keys).
-   */
-  struct TMH_EXCHANGES_FindOperation *fo;
-
-  /**
-   * Handle to the exchange (set after exchange_found_cb()).
-   */
-  struct TALER_EXCHANGE_Handle *eh;
-
-  /**
-   * 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;
-
-  /**
-   * Message to return.
-   */
-  struct MHD_Response *response;
-
-  /**
-   * HTTP status code to return in combination with @e response.
-   */
-  unsigned int response_code;
-
-  /**
-   * Set to #GNUNET_YES if @e connection was suspended.
-   */
-  int suspended;
-
-};
-
-
-/**
- * Kept in a DLL while suspended.
- */
-static struct PickupContext *pc_head;
-
-/**
- * Kept in a DLL while suspended.
- */
-static struct PickupContext *pc_tail;
-
-
-/**
- * We are shutting down, force resuming all suspended pickup operations.
- */
-void
-MH_force_tip_pickup_resume ()
-{
-  struct PickupContext *pc;
-
-  while (NULL != (pc = pc_head))
-  {
-    GNUNET_assert (GNUNET_YES == pc->suspended);
-    GNUNET_CONTAINER_DLL_remove (pc_head,
-                                 pc_tail,
-                                 pc);
-    MHD_resume_connection (pc->connection);
-  }
-}
-
-
-/**
- * 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++)
-    {
-      struct PlanchetDetail *pd = &pc->planchets[i];
-      GNUNET_free_non_null (pd->coin_ev);
-      if (NULL != pd->wh)
-      {
-        TALER_EXCHANGE_withdraw2_cancel (pd->wh);
-        pd->wh = NULL;
-      }
-      if (NULL != pd->blind_sig)
-      {
-        json_decref (pd->blind_sig);
-        pd->blind_sig = NULL;
-      }
-    }
-    GNUNET_free (pc->planchets);
-    pc->planchets = NULL;
-  }
-  if (NULL != pc->fo)
-  {
-    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
-    pc->fo = NULL;
-  }
-  TALER_MHD_parse_post_cleanup_callback (pc->json_parse_context);
-  GNUNET_free_non_null (pc->exchange_url);
-  if (NULL != pc->response)
-  {
-    MHD_destroy_response (pc->response);
-    pc->response = NULL;
-  }
-  GNUNET_free (pc);
-}
-
-
-/**
- * Resume processing of a suspended @a pc.
- *
- * @param pc a suspended pickup operation
- */
-static void
-resume_pc (struct PickupContext *pc)
-{
-  for (unsigned int i = 0; i<pc->planchets_len; i++)
-  {
-    struct PlanchetDetail *pd = &pc->planchets[i];
-
-    if (NULL != pd->wh)
-    {
-      TALER_EXCHANGE_withdraw2_cancel (pc->planchets[i].wh);
-      pc->planchets[i].wh = NULL;
-    }
-  }
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  GNUNET_CONTAINER_DLL_remove (pc_head,
-                               pc_tail,
-                               pc);
-  MHD_resume_connection (pc->connection);
-  TMH_trigger_daemon ();
-}
-
-
-/**
- * Function called with the result of our attempt to withdraw
- * the coin for a tip.
- *
- * @param cls closure
- * @param hr HTTP response data
- * @param blind_sig blind signature over the coin, NULL on error
- */
-static void
-withdraw_cb (void *cls,
-             const struct TALER_EXCHANGE_HttpResponse *hr,
-             const struct GNUNET_CRYPTO_RsaSignature *blind_sig)
-{
-  struct PlanchetDetail *pd = cls;
-  struct PickupContext *pc = pd->pc;
-  json_t *ja;
-
-  pd->wh = NULL;
-  if (NULL == blind_sig)
-  {
-    pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
-    pc->response = TALER_MHD_make_json_pack (
-      (NULL != hr->reply)
-      ? "{s:s, s:I, s:I, s:I, s:O}"
-      : "{s:s, s:I, s:I, s:I}",
-      "hint", "failed to withdraw coin from exchange",
-      "code", (json_int_t) TALER_EC_TIP_PICKUP_WITHDRAW_FAILED_AT_EXCHANGE,
-      "exchange_http_status", (json_int_t) hr->http_status,
-      "exchange_code", (json_int_t) hr->ec,
-      "exchange_reply", hr->reply);
-    resume_pc (pc);
-    return;
-  }
-  /* FIXME: persisit blind_sig in our database!?
-     (or at least _all_ of them once we have them all?) */
-  pd->blind_sig = GNUNET_JSON_from_rsa_signature (blind_sig);
-  GNUNET_assert (NULL != pd->blind_sig);
-  for (unsigned int i = 0; i<pc->planchets_len; i++)
-    if (NULL != pc->planchets[i].wh)
-      return;
-  /* All done, build final response */
-  ja = json_array ();
-  GNUNET_assert (NULL != ja);
-  for (unsigned int i = 0; i<pc->planchets_len; i++)
-  {
-    struct PlanchetDetail *pd = &pc->planchets[i];
-
-    GNUNET_assert (0 ==
-                   json_array_append_new (ja,
-                                          json_pack ("{s:o}",
-                                                     "blind_sig",
-                                                     pd->blind_sig)));
-    pd->blind_sig = NULL;
-  }
-  pc->response_code = MHD_HTTP_OK;
-  pc->response = TALER_MHD_make_json_pack ("{s:o}",
-                                           "blind_sigs",
-                                           ja);
-  resume_pc (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 pc pickup context
- */
-static void
-run_pickup (struct PickupContext *pc)
-{
-  struct TALER_ReservePrivateKeyP reserve_priv;
-  enum TALER_ErrorCode ec;
-
-  db->preflight (db->cls);
-  ec = db->pickup_tip_TR (db->cls,
-                          &pc->total,
-                          &pc->tip_id,
-                          &pc->pickup_id,
-                          &reserve_priv);
-  if (TALER_EC_NONE != ec)
-  {
-    const char *human;
-
-    switch (ec)
-    {
-    case TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN:
-      pc->response_code = MHD_HTTP_NOT_FOUND;
-      human = "tip identifier not known to this service";
-      break;
-    case TALER_EC_TIP_PICKUP_NO_FUNDS:
-      pc->response_code = MHD_HTTP_CONFLICT;
-      human = "withdrawn funds exceed amounts approved for tip";
-      break;
-    default:
-      pc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
-      human = "database failure";
-      break;
-    }
-    pc->response = TALER_MHD_make_error (ec,
-                                         human);
-    resume_pc (pc);
-    return;
-  }
-  for (unsigned int i = 0; i<pc->planchets_len; i++)
-  {
-    struct PlanchetDetail *pd = &pc->planchets[i];
-    struct TALER_PlanchetDetail pdx = {
-      .denom_pub_hash = pd->h_denom_pub,
-      .coin_ev = pd->coin_ev,
-      .coin_ev_size = pd->coin_ev_size,
-    };
-
-    pd->wh = TALER_EXCHANGE_withdraw2 (pc->eh,
-                                       &pdx,
-                                       &reserve_priv,
-                                       &withdraw_cb,
-                                       pd);
-    if (NULL == pd->wh)
-    {
-      GNUNET_break (0);
-      pc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
-      pc->response = TALER_MHD_make_error (TALER_EC_TIP_PICKUP_WITHDRAW_FAILED,
-                                           "could not inititate withdrawal");
-      resume_pc (pc);
-      return;
-    }
-  }
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.
- *
- * @param cls closure with the `struct PickupContext`
- * @param hr HTTP response details
- * @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,
-                   const struct TALER_EXCHANGE_HttpResponse *hr,
-                   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 TALER_Amount total;
-  int ae;
-
-  pc->fo = NULL;
-  if (NULL == eh)
-  {
-    pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
-    pc->response = TALER_MHD_make_json_pack (
-      (NULL != hr->reply)
-      ? "{s:s, s:I, s:I, s:I, s:O}"
-      : "{s:s, s:I, s:I, s:I}",
-      "hint", "failed to contact exchange, check URL",
-      "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_DOWN,
-      "exchange_http_status", (json_int_t) hr->http_status,
-      "exchange_code", (json_int_t) hr->ec,
-      "exchange_reply", hr->reply);
-    resume_pc (pc);
-    return;
-  }
-  keys = TALER_EXCHANGE_get_keys (eh);
-  if (NULL == keys)
-  {
-    pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
-    pc->response = TALER_MHD_make_json_pack (
-      (NULL != hr->reply)
-      ? "{s:s, s:I, s:I, s:I, s:O}"
-      : "{s:s, s:I, s:I, s:I}",
-      "hint", "could not obtain denomination keys from exchange, check URL",
-      "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEYS,
-      "exchange_http_status", (json_int_t) hr->http_status,
-      "exchange_code", (json_int_t) hr->ec,
-      "exchange_reply", hr->reply);
-    resume_pc (pc);
-    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);
-  {
-    struct GNUNET_HashContext *hc;
-
-    hc = GNUNET_CRYPTO_hash_context_start ();
-    for (unsigned int i = 0; i<pc->planchets_len; i++)
-    {
-      struct PlanchetDetail *pd = &pc->planchets[i];
-      struct TALER_Amount amount_with_fee;
-      const struct TALER_EXCHANGE_DenomPublicKey *dk;
-
-      dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
-                                                        &pd->h_denom_pub);
-      if (NULL == dk)
-      {
-        pc->response_code = MHD_HTTP_NOT_FOUND;
-        pc->response
-          = TALER_MHD_make_json_pack (
-              "{s:s, s:I}"
-              "hint",
-              "could not find matching denomination key",
-              "code",
-              (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEY);
-        resume_pc (pc);
-        GNUNET_CRYPTO_hash_context_abort (hc);
-        return;
-      }
-      GNUNET_CRYPTO_hash_context_read (hc,
-                                       &pd->h_denom_pub,
-                                       sizeof (struct GNUNET_HashCode));
-      GNUNET_CRYPTO_hash_context_read (hc,
-                                       pd->coin_ev,
-                                       pd->coin_ev_size);
-      if (0 >
-          TALER_amount_add (&amount_with_fee,
-                            &dk->value,
-                            &dk->fee_withdraw))
-      {
-        ae = GNUNET_YES;
-      }
-      if (0 == i)
-      {
-        total = amount_with_fee;
-      }
-      else
-      {
-        if (0 >
-            TALER_amount_add (&total,
-                              &total,
-                              &amount_with_fee))
-        {
-          ae = GNUNET_YES;
-        }
-      }
-    }
-    GNUNET_CRYPTO_hash_context_finish (hc,
-                                       &pc->pickup_id);
-  }
-  if (GNUNET_YES == ae)
-  {
-    pc->response_code = MHD_HTTP_BAD_REQUEST;
-    pc->response
-      = TALER_MHD_make_json_pack (
-          "{s:s, s:I}"
-          "hint",
-          "error computing total value of the tip",
-          "code",
-          (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_AMOUNT_OVERFLOW);
-    resume_pc (pc);
-    return;
-  }
-  pc->eh = eh;
-  pc->total = total;
-  run_pickup (pc);
-}
-
-
-/**
- * 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 MHD_RESULT
-prepare_pickup (struct PickupContext *pc)
-{
-  enum GNUNET_DB_QueryStatus qs;
-
-  db->preflight (db->cls);
-  /* FIXME: do not pass NULL's, *do* get the
-     data from the DB, we may be able to avoid
-     most of the processing if we already have
-     a valid result! */
-  qs = db->lookup_tip_by_id (db->cls,
-                             &pc->tip_id,
-                             &pc->exchange_url,
-                             NULL, NULL, 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 TALER_MHD_reply_with_error (pc->connection,
-                                       response_code,
-                                       ec,
-                                       "Could not determine exchange URL for 
the given tip id");
-
-  }
-  pc->fo = TMH_EXCHANGES_find_exchange (pc->exchange_url,
-                                        NULL,
-                                        GNUNET_NO,
-                                        &exchange_found_cb,
-                                        pc);
-  if (NULL == pc->fo)
-  {
-    return TALER_MHD_reply_with_error (pc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_INTERNAL_INVARIANT_FAILURE,
-                                       "consult server logs");
-  }
-  /* continued asynchronously in exchange_found_cb() */
-  GNUNET_assert (GNUNET_NO == pc->suspended);
-  pc->suspended = GNUNET_YES;
-  GNUNET_CONTAINER_DLL_insert (pc_head,
-                               pc_tail,
-                               pc);
-  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 enum GNUNET_GenericReturnValue
-parse_planchet (struct MHD_Connection *connection,
-                const json_t *planchet,
-                struct PlanchetDetail *pd)
-{
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
-                                 &pd->h_denom_pub),
-    GNUNET_JSON_spec_varsize ("coin_ev",
-                              (void **) &pd->coin_ev,
-                              &pd->coin_ev_size),
-    GNUNET_JSON_spec_end ()
-  };
-
-  return TALER_MHD_parse_json_data (connection,
-                                    planchet,
-                                    spec);
-}
-
-
-/**
- * Manages a POST /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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_tip_pickup (struct TMH_RequestHandler *rh,
-                       struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size,
-                       struct MerchantInstance *mi)
-{
-  enum GNUNET_GenericReturnValue 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->response)
-  {
-    MHD_RESULT ret;
-
-    ret = MHD_queue_response (connection,
-                              pc->response_code,
-                              pc->response);
-    MHD_destroy_response (pc->response);
-    pc->response = NULL;
-    return ret;
-  }
-  res = TALER_MHD_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 = TALER_MHD_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 TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_TIP_PICKUP_EXCHANGE_TOO_MANY_PLANCHETS,
-                                       "per request limit of 1024 planchets 
exceeded");
-  }
-  if (0 == pc->planchets_len)
-  {
-    GNUNET_JSON_parse_free (spec);
-    json_decref (root);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MALFORMED,
-                                       "no planchets specified");
-  }
-  db->preflight (db->cls);
-  pc->planchets = GNUNET_new_array (pc->planchets_len,
-                                    struct PlanchetDetail);
-  for (unsigned int i = 0; i<pc->planchets_len; i++)
-  {
-    pc->planchets[i].pc = pc;
-    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;
-  {
-    MHD_RESULT ret;
-
-    ret = prepare_pickup (pc);
-    GNUNET_JSON_parse_free (spec);
-    json_decref (root);
-    return ret;
-  }
-}
diff --git a/src/backend/taler-merchant-httpd_tip-pickup.h 
b/src/backend/taler-merchant-httpd_tip-pickup.h
deleted file mode 100644
index 6fdba31..0000000
--- a/src/backend/taler-merchant-httpd_tip-pickup.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
-  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.h
- * @brief headers for /tip-pickup handler
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_TIP_PICKUP_H
-#define TALER_MERCHANT_HTTPD_TIP_PICKUP_H
-#include <microhttpd.h>
-#include "taler-merchant-httpd.h"
-
-
-/**
- * We are shutting down, force resuming all suspended pickup operations.
- */
-void
-MH_force_tip_pickup_resume (void);
-
-
-/**
- * Manages a POST /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
-* @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_tip_pickup (struct TMH_RequestHandler *rh,
-                       struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size,
-                       struct MerchantInstance *mi);
-
-
-/**
- * Manages a GET /tip-pickup call.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_tip_pickup_get (struct TMH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size,
-                           struct MerchantInstance *mi);
-
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_tip-pickup_get.c 
b/src/backend/taler-merchant-httpd_tip-pickup_get.c
deleted file mode 100644
index 42066e3..0000000
--- a/src/backend/taler-merchant-httpd_tip-pickup_get.c
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2017-2020 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_exchanges.h"
-#include "taler-merchant-httpd_tip-pickup.h"
-
-
-/**
- * Manages a GET /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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_tip_pickup_get (struct TMH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size,
-                           struct MerchantInstance *mi)
-{
-  const char *tip_id_str;
-  char *exchange_url;
-  json_t *extra;
-  struct GNUNET_HashCode tip_id;
-  struct TALER_Amount tip_amount;
-  struct TALER_Amount tip_amount_left;
-  struct GNUNET_TIME_Absolute timestamp;
-  struct GNUNET_TIME_Absolute timestamp_expire;
-  MHD_RESULT ret;
-  enum GNUNET_DB_QueryStatus qs;
-
-  tip_id_str = MHD_lookup_connection_value (connection,
-                                            MHD_GET_ARGUMENT_KIND,
-                                            "tip_id");
-
-  if (NULL == tip_id_str)
-  {
-    /* tip_id is required but missing */
-    GNUNET_break_op (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MISSING,
-                                       "tip_id required");
-  }
-
-  if (GNUNET_OK !=
-      GNUNET_CRYPTO_hash_from_string (tip_id_str,
-                                      &tip_id))
-  {
-    /* tip_id has wrong encoding */
-    GNUNET_break_op (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MALFORMED,
-                                       "tip_id malformed");
-  }
-
-  db->preflight (db->cls);
-  qs = db->lookup_tip_by_id (db->cls,
-                             &tip_id,
-                             &exchange_url,
-                             &extra,
-                             &tip_amount,
-                             &tip_amount_left,
-                             &timestamp);
-
-  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 TALER_MHD_reply_with_error (connection,
-                                       response_code,
-                                       ec,
-                                       "Could not determine exchange URL for 
the given tip id");
-  }
-
-  timestamp_expire = GNUNET_TIME_absolute_add (timestamp,
-                                               GNUNET_TIME_UNIT_DAYS);
-
-  ret = TALER_MHD_reply_json_pack (
-    connection,
-    MHD_HTTP_OK,
-    "{s:s, s:o, s:o, s:o, s:o, s:o}",
-    "exchange_url", exchange_url,
-    "amount", TALER_JSON_from_amount (&tip_amount),
-    "amount_left", TALER_JSON_from_amount (&tip_amount_left),
-    "stamp_created", GNUNET_JSON_from_time_abs (timestamp),
-    "stamp_expire", GNUNET_JSON_from_time_abs (timestamp_expire),
-    "extra", extra);
-
-  GNUNET_free (exchange_url);
-  json_decref (extra);
-  return ret;
-}
diff --git a/src/backend/taler-merchant-httpd_tip-query.c 
b/src/backend/taler-merchant-httpd_tip-query.c
deleted file mode 100644
index f7aa0ab..0000000
--- a/src/backend/taler-merchant-httpd_tip-query.c
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2018 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-query.c
- * @brief implement API for authorizing tips to be paid to visitors
- * @author Christian Grothoff
- * @author Florian Dold
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_tip-query.h"
-#include "taler-merchant-httpd_tip-reserve-helper.h"
-
-
-/**
- * Maximum number of retries for database operations.
- */
-#define MAX_RETRIES 5
-
-
-/**
- * Internal per-request state for processing tip queries.
- */
-struct TipQueryContext
-{
-  /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Merchant instance to use.
-   */
-  const char *instance;
-
-  /**
-   * Context for checking the tipping reserve's status.
-   */
-  struct TMH_CheckTipReserve ctr;
-
-  /**
-   * #GNUNET_YES if the tip query has already been processed
-   * and we can queue the response.
-   */
-  int processed;
-
-};
-
-
-/**
- * Custom cleanup routine for a `struct TipQueryContext`.
- *
- * @param hc the `struct TMH_JsonParseContext` to clean up.
- */
-static void
-cleanup_tqc (struct TM_HandlerContext *hc)
-{
-  struct TipQueryContext *tqc = (struct TipQueryContext *) hc;
-
-  TMH_check_tip_reserve_cleanup (&tqc->ctr);
-  GNUNET_free (tqc);
-}
-
-
-/**
- * We've been resumed after processing the reserve data from the
- * exchange without error. Generate the final response.
- *
- * @param tqc context for which to generate the response.
- */
-static int
-generate_final_response (struct TipQueryContext *tqc)
-{
-  struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
-  struct TALER_Amount amount_available;
-
-  GNUNET_CRYPTO_eddsa_key_get_public (&tqc->ctr.reserve_priv.eddsa_priv,
-                                      &reserve_pub);
-  if (0 >
-      TALER_amount_subtract (&amount_available,
-                             &tqc->ctr.amount_deposited,
-                             &tqc->ctr.amount_withdrawn))
-  {
-    char *a1;
-    char *a2;
-
-    GNUNET_break_op (0);
-    a1 = TALER_amount_to_string (&tqc->ctr.amount_deposited);
-    a2 = TALER_amount_to_string (&tqc->ctr.amount_withdrawn);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "amount overflow, deposited %s but withdrawn %s\n",
-                a1,
-                a2);
-    GNUNET_free (a2);
-    GNUNET_free (a1);
-    return TALER_MHD_reply_with_error (
-      tqc->ctr.connection,
-      MHD_HTTP_INTERNAL_SERVER_ERROR,
-      TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_INCONSISTENT,
-      "Exchange returned invalid reserve history (amount overflow)");
-  }
-  return TALER_MHD_reply_json_pack (
-    tqc->ctr.connection,
-    MHD_HTTP_OK,
-    "{s:o, s:o, s:o, s:o, s:o}",
-    "reserve_pub",
-    GNUNET_JSON_from_data_auto (&reserve_pub),
-    "reserve_expiration",
-    GNUNET_JSON_from_time_abs (tqc->ctr.reserve_expiration),
-    "amount_authorized",
-    TALER_JSON_from_amount (&tqc->ctr.amount_authorized),
-    "amount_picked_up",
-    TALER_JSON_from_amount (&tqc->ctr.amount_withdrawn),
-    "amount_available",
-    TALER_JSON_from_amount (&amount_available));
-}
-
-
-/**
- * Handle a "/tip-query" 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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_tip_query (struct TMH_RequestHandler *rh,
-                      struct MHD_Connection *connection,
-                      void **connection_cls,
-                      const char *upload_data,
-                      size_t *upload_data_size,
-                      struct MerchantInstance *mi)
-{
-  struct TipQueryContext *tqc;
-
-  if (NULL == *connection_cls)
-  {
-    tqc = GNUNET_new (struct TipQueryContext);
-    tqc->hc.cc = &cleanup_tqc;
-    tqc->ctr.connection = connection;
-    *connection_cls = tqc;
-  }
-  else
-  {
-    tqc = *connection_cls;
-  }
-
-  if (0 != tqc->ctr.response_code)
-  {
-    MHD_RESULT res;
-
-    /* We are *done* processing the request, just queue the response (!) */
-    if (UINT_MAX == tqc->ctr.response_code)
-    {
-      GNUNET_break (0);
-      return MHD_NO; /* hard error */
-    }
-    res = MHD_queue_response (connection,
-                              tqc->ctr.response_code,
-                              tqc->ctr.response);
-    MHD_destroy_response (tqc->ctr.response);
-    tqc->ctr.response = NULL;
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Queueing response (%u) for /tip-query (%s).\n",
-                (unsigned int) tqc->ctr.response_code,
-                res ? "OK" : "FAILED");
-    return res;
-  }
-
-  if (GNUNET_YES == tqc->processed)
-  {
-    /* We've been here before, so TMH_check_tip_reserve() must have
-       finished and left the result for us. Finish processing. */
-    return generate_final_response (tqc);
-  }
-
-  if (NULL == mi->tip_exchange)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Instance `%s' not configured for tipping\n",
-                mi->id);
-    return TALER_MHD_reply_with_error (
-      connection,
-      MHD_HTTP_PRECONDITION_FAILED,
-      TALER_EC_TIP_QUERY_INSTANCE_DOES_NOT_TIP,
-      "exchange for tipping not configured for the instance");
-  }
-  tqc->ctr.reserve_priv = mi->tip_reserve;
-
-  {
-    enum GNUNET_DB_QueryStatus qs;
-
-    for (unsigned int i = 0; i<MAX_RETRIES; i++)
-    {
-      db->preflight (db->cls);
-      qs = db->get_authorized_tip_amount (db->cls,
-                                          &tqc->ctr.reserve_priv,
-                                          &tqc->ctr.amount_authorized);
-      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-        break;
-    }
-    if (0 > qs)
-    {
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_TIP_QUERY_DB_ERROR,
-                                         "Merchant database error");
-    }
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-    {
-      /* we'll set amount_authorized to zero later once
-         we know the currency */
-      tqc->ctr.none_authorized = GNUNET_YES;
-    }
-  }
-
-  tqc->processed = GNUNET_YES;
-  TMH_check_tip_reserve (&tqc->ctr,
-                         mi->tip_exchange);
-  return MHD_YES;
-}
-
-
-/* end of taler-merchant-httpd_tip-query.c */
diff --git a/src/backend/taler-merchant-httpd_tip-reserve-helper.c 
b/src/backend/taler-merchant-httpd_tip-reserve-helper.c
deleted file mode 100644
index e104e08..0000000
--- a/src/backend/taler-merchant-httpd_tip-reserve-helper.c
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2018--2020 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-reserve-helper.c
- * @brief helper functions to check the status of a tipping reserve
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler-merchant-httpd_tip-reserve-helper.h"
-
-
-/**
- * Head of active ctr context DLL.
- */
-static struct TMH_CheckTipReserve *ctr_head;
-
-/**
- * Tail of active ctr context DLL.
- */
-static struct TMH_CheckTipReserve *ctr_tail;
-
-
-/**
- * Resume connection underlying @a ctr.
- *
- * @param ctr what to resume
- */
-static void
-resume_ctr (struct TMH_CheckTipReserve *ctr)
-{
-  GNUNET_assert (GNUNET_YES == ctr->suspended);
-  GNUNET_CONTAINER_DLL_remove (ctr_head,
-                               ctr_tail,
-                               ctr);
-  MHD_resume_connection (ctr->connection);
-  TMH_trigger_daemon (); /* we resumed, kick MHD */
-}
-
-
-/**
- * Resume the given context and send the given response.  Stores the response
- * in the @a ctr and signals MHD to resume the connection.  Also ensures MHD
- * runs immediately.
- *
- * @param ctr tip reserve query helper context
- * @param response_code response code to use
- * @param response response data to send back
- */
-static void
-resume_with_response (struct TMH_CheckTipReserve *ctr,
-                      unsigned int response_code,
-                      struct MHD_Response *response)
-{
-  ctr->response_code = response_code;
-  ctr->response = response;
-  resume_ctr (ctr);
-  ctr->suspended = GNUNET_NO;
-}
-
-
-/**
- * Function called with the result of the /reserve/status request
- * for the tipping reserve.  Update our database balance with the
- * result.
- *
- * @param cls closure with a `struct TMH_CheckTipReserve *'
- * @param hr HTTP response details
- * @param balance current balance in the reserve, NULL on error
- * @param history_length number of entries in the transaction history, 0 on 
error
- * @param history detailed transaction history, NULL on error
- */
-static void
-handle_status (void *cls,
-               const struct TALER_EXCHANGE_HttpResponse *hr,
-               const struct TALER_Amount *balance,
-               unsigned int history_length,
-               const struct TALER_EXCHANGE_ReserveHistory *history)
-{
-  struct TMH_CheckTipReserve *ctr = cls;
-
-  ctr->rsh = NULL;
-  ctr->reserve_expiration = GNUNET_TIME_UNIT_ZERO_ABS;
-  if (MHD_HTTP_NOT_FOUND == hr->http_status)
-  {
-    resume_with_response (
-      ctr,
-      MHD_HTTP_SERVICE_UNAVAILABLE,
-      TALER_MHD_make_json_pack (
-        "{s:I, s:I, s:s, s:I, s:O}",
-        "code", (json_int_t) TALER_EC_TIP_QUERY_RESERVE_UNKNOWN_TO_EXCHANGE,
-        "exchange_http_status", hr->http_status,
-        "hint", "tipping reserve unknown at exchange",
-        "exchange_code", hr->ec,
-        "exchange_reply", hr->reply));
-    return;
-  }
-  if (MHD_HTTP_OK != hr->http_status)
-  {
-    GNUNET_break_op (0);
-    resume_with_response (
-      ctr,
-      MHD_HTTP_FAILED_DEPENDENCY,
-      TALER_MHD_make_json_pack (
-        "{s:I, s:I, s:s, s:I, s:O}",
-        "code", (json_int_t) TALER_EC_TIP_QUERY_RESERVE_HISTORY_FAILED,
-        "exchange_http_status", hr->http_status,
-        "hint", "exchange failed to provide reserve history",
-        "exchange_code", (json_int_t) hr->ec,
-        "exchange_reply", hr->reply));
-    return;
-  }
-
-  if (0 == history_length)
-  {
-    GNUNET_break_op (0);
-    resume_with_response (ctr,
-                          MHD_HTTP_FAILED_DEPENDENCY,
-                          TALER_MHD_make_error (
-                            TALER_EC_TIP_QUERY_RESERVE_HISTORY_FAILED_EMPTY,
-                            "Exchange returned empty reserve history"));
-    return;
-  }
-
-  {
-    unsigned int found = UINT_MAX;
-
-    for (unsigned int i = 0; i<history_length; i++)
-      if (TALER_EXCHANGE_RTT_CREDIT == history[i].type)
-        found = i;
-    if (UINT_MAX == found)
-    {
-      GNUNET_break_op (0);
-      resume_with_response (ctr,
-                            MHD_HTTP_FAILED_DEPENDENCY,
-                            TALER_MHD_make_error (
-                              
TALER_EC_TIP_QUERY_RESERVE_HISTORY_INVALID_NO_DEPOSIT,
-                              "Exchange returned invalid reserve history"));
-      return;
-    }
-
-    if (0 != strcasecmp (TMH_currency,
-                         history[found].amount.currency))
-    {
-      GNUNET_break_op (0);
-      resume_with_response (ctr,
-                            MHD_HTTP_SERVICE_UNAVAILABLE,
-                            TALER_MHD_make_error (
-                              TALER_EC_TIP_QUERY_RESERVE_CURRENCY_MISMATCH,
-                              "Exchange currency unexpected"));
-      return;
-    }
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (history[found].amount.currency,
-                                          &ctr->amount_withdrawn));
-  }
-
-  if (GNUNET_YES == ctr->none_authorized)
-    ctr->amount_authorized = ctr->amount_withdrawn; /* aka zero */
-  ctr->amount_deposited = ctr->amount_withdrawn; /* aka zero */
-
-  /* Update DB based on status! */
-  for (unsigned int i = 0; i<history_length; i++)
-  {
-    const struct TALER_EXCHANGE_ReserveHistory *hi = &history[i];
-
-    switch (hi->type)
-    {
-    case TALER_EXCHANGE_RTT_CREDIT:
-      {
-        enum GNUNET_DB_QueryStatus qs;
-        struct GNUNET_HashCode uuid;
-        struct GNUNET_TIME_Absolute deposit_expiration;
-
-        if (0 >
-            TALER_amount_add (&ctr->amount_deposited,
-                              &ctr->amount_deposited,
-                              &hi->amount))
-        {
-          GNUNET_break_op (0);
-          resume_with_response (
-            ctr,
-            MHD_HTTP_FAILED_DEPENDENCY,
-            TALER_MHD_make_error (
-              TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_DEPOSIT,
-              "Exchange returned invalid reserve history (amount overflow)"));
-          return;
-        }
-        deposit_expiration = GNUNET_TIME_absolute_add (
-          hi->details.in_details.timestamp,
-          ctr->idle_reserve_expiration_time);
-        /* We're interested in the latest DEPOSIT timestamp, since this 
determines the
-         * reserve's expiration date. Note that the history isn't 
chronologically ordered. */
-        ctr->reserve_expiration = GNUNET_TIME_absolute_max (
-          ctr->reserve_expiration,
-          deposit_expiration);
-        GNUNET_CRYPTO_hash (hi->details.in_details.wire_reference,
-                            hi->details.in_details.wire_reference_size,
-                            &uuid);
-        db->preflight (db->cls);
-        qs = db->enable_tip_reserve_TR (db->cls,
-                                        &ctr->reserve_priv,
-                                        &uuid,
-                                        &hi->amount,
-                                        deposit_expiration);
-
-        if (0 > qs)
-        {
-          /* This is not inherently fatal for the client's request, so we 
merely log it */
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Database error updating tipping reserve status: %d\n",
-                      qs);
-        }
-      }
-      break;
-    case TALER_EXCHANGE_RTT_WITHDRAWAL:
-      if (0 >
-          TALER_amount_add (&ctr->amount_withdrawn,
-                            &ctr->amount_withdrawn,
-                            &hi->amount))
-      {
-        GNUNET_break_op (0);
-        resume_with_response (
-          ctr,
-          MHD_HTTP_FAILED_DEPENDENCY,
-          TALER_MHD_make_error (
-            TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_WITHDRAW,
-            "Exchange returned invalid reserve history (amount overflow)"));
-        return;
-      }
-      break;
-    case TALER_EXCHANGE_RTT_RECOUP:
-      {
-        enum GNUNET_DB_QueryStatus qs;
-        struct GNUNET_HashContext *hc;
-        struct GNUNET_HashCode uuid;
-        struct GNUNET_TIME_Absolute deposit_expiration;
-        struct GNUNET_TIME_AbsoluteNBO de;
-
-        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                    "Encountered unexpected recoup operation on tipping 
reserve\n");
-        /* While unexpected, we can simply count these like deposits. */
-        if (0 >
-            TALER_amount_add (&ctr->amount_deposited,
-                              &ctr->amount_deposited,
-                              &hi->amount))
-        {
-          GNUNET_break_op (0);
-          resume_with_response (
-            ctr,
-            MHD_HTTP_FAILED_DEPENDENCY,
-            TALER_MHD_make_error (
-              TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_RECOUP,
-              "Exchange returned invalid reserve history (amount overflow)"));
-          return;
-        }
-        deposit_expiration = GNUNET_TIME_absolute_add (
-          hi->details.recoup_details.timestamp,
-          ctr->idle_reserve_expiration_time);
-        ctr->reserve_expiration = GNUNET_TIME_absolute_max (
-          ctr->reserve_expiration,
-          deposit_expiration);
-        de = GNUNET_TIME_absolute_hton (deposit_expiration);
-        hc = GNUNET_CRYPTO_hash_context_start ();
-        GNUNET_CRYPTO_hash_context_read (
-          hc,
-          &hi->details.recoup_details.coin_pub,
-          sizeof (struct TALER_CoinSpendPublicKeyP));
-        GNUNET_CRYPTO_hash_context_read (hc,
-                                         &de,
-                                         sizeof (de));
-        GNUNET_CRYPTO_hash_context_finish (hc,
-                                           &uuid);
-        db->preflight (db->cls);
-        qs = db->enable_tip_reserve_TR (db->cls,
-                                        &ctr->reserve_priv,
-                                        &uuid,
-                                        &hi->amount,
-                                        deposit_expiration);
-        if (0 > qs)
-        {
-          /* This is not inherently fatal for the client's request, so we 
merely log it */
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Database error updating tipping reserve status: %d\n",
-                      qs);
-        }
-      }
-      break;
-    case TALER_EXCHANGE_RTT_CLOSE:
-      /* We count 'closing' amounts just like withdrawals */
-      if (0 >
-          TALER_amount_add (&ctr->amount_withdrawn,
-                            &ctr->amount_withdrawn,
-                            &hi->amount))
-      {
-        GNUNET_break_op (0);
-        resume_with_response (
-          ctr,
-          MHD_HTTP_FAILED_DEPENDENCY,
-          TALER_MHD_make_error (
-            TALER_EC_TIP_QUERY_RESERVE_HISTORY_ARITHMETIC_ISSUE_CLOSED,
-            "Exchange returned invalid reserve history (amount overflow)"));
-        return;
-      }
-      break;
-    }
-  }
-
-  /* normal, non-error continuation */
-  resume_with_response (ctr,
-                        0,
-                        NULL);
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.  Given the exchange handle, we will then interrogate
- * the exchange about the status of the tipping reserve.
- *
- * @param cls closure with a `struct TMH_CheckTipReserve *`
- * @param hr HTTP response details
- * @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_cont (void *cls,
-               const struct TALER_EXCHANGE_HttpResponse *hr,
-               struct TALER_EXCHANGE_Handle *eh,
-               const struct TALER_Amount *wire_fee,
-               int exchange_trusted)
-{
-  struct TMH_CheckTipReserve *ctr = cls;
-  struct TALER_ReservePublicKeyP reserve_pub;
-  const struct TALER_EXCHANGE_Keys *keys;
-
-  ctr->fo = NULL;
-  if (NULL == eh)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to contact exchange configured for tipping!\n");
-    resume_with_response (ctr,
-                          MHD_HTTP_FAILED_DEPENDENCY,
-                          TALER_MHD_make_error (
-                            
TALER_EC_TIP_QUERY_RESERVE_STATUS_FAILED_EXCHANGE_DOWN,
-                            "Unable to obtain /keys from exchange"));
-    return;
-  }
-  keys = TALER_EXCHANGE_get_keys (eh);
-  GNUNET_assert (NULL != keys);
-  ctr->idle_reserve_expiration_time
-    = keys->reserve_closing_delay;
-  GNUNET_CRYPTO_eddsa_key_get_public (&ctr->reserve_priv.eddsa_priv,
-                                      &reserve_pub.eddsa_pub);
-  ctr->rsh = TALER_EXCHANGE_reserves_get (eh,
-                                          &reserve_pub,
-                                          &handle_status,
-                                          ctr);
-}
-
-
-/**
- * Check the status of the given reserve at the given exchange.
- * Suspends the MHD connection while this is happening and resumes
- * processing once we know the reserve status (or once an error
- * code has been determined).
- *
- * @param[in,out] ctr context for checking the reserve status
- * @param tip_exchange the URL of the exchange to query
- */
-void
-TMH_check_tip_reserve (struct TMH_CheckTipReserve *ctr,
-                       const char *tip_exchange)
-{
-  MHD_suspend_connection (ctr->connection);
-  ctr->suspended = GNUNET_YES;
-  GNUNET_CONTAINER_DLL_insert (ctr_head,
-                               ctr_tail,
-                               ctr);
-  db->preflight (db->cls);
-  ctr->fo = TMH_EXCHANGES_find_exchange (tip_exchange,
-                                         NULL,
-                                         GNUNET_NO,
-                                         &exchange_cont,
-                                         ctr);
-  if (NULL == ctr->fo)
-  {
-    GNUNET_break (0);
-    resume_with_response (ctr,
-                          MHD_HTTP_INTERNAL_SERVER_ERROR,
-                          TALER_MHD_make_error (
-                            TALER_EC_INTERNAL_INVARIANT_FAILURE,
-                            "Unable to find exchange handle"));
-  }
-}
-
-
-/**
- * Clean up any state that might be left in @a ctr.
- *
- * @param[in] context to clean up
- */
-void
-TMH_check_tip_reserve_cleanup (struct TMH_CheckTipReserve *ctr)
-{
-  if (NULL != ctr->rsh)
-  {
-    TALER_EXCHANGE_reserves_get_cancel (ctr->rsh);
-    ctr->rsh = NULL;
-  }
-  if (NULL != ctr->fo)
-  {
-    TMH_EXCHANGES_find_exchange_cancel (ctr->fo);
-    ctr->fo = NULL;
-  }
-  if (NULL != ctr->response)
-  {
-    MHD_destroy_response (ctr->response);
-    ctr->response = NULL;
-  }
-  if (MHD_YES == ctr->suspended)
-  {
-    resume_ctr (ctr);
-    ctr->suspended = GNUNET_NO;
-  }
-}
-
-
-/**
- * Force all tip reserve helper contexts to be resumed as we are about to shut
- * down MHD.
- */
-void
-MH_force_trh_resume ()
-{
-  struct TMH_CheckTipReserve *n;
-
-  for (struct TMH_CheckTipReserve *ctr = ctr_head;
-       NULL != ctr;
-       ctr = n)
-  {
-    n = ctr->next;
-    resume_ctr (ctr);
-    ctr->suspended = GNUNET_SYSERR;
-  }
-}
-
-
-/* end of taler-merchant-httpd_tip-reserve-helper.c */
diff --git a/src/backend/taler-merchant-httpd_tip-reserve-helper.h 
b/src/backend/taler-merchant-httpd_tip-reserve-helper.h
deleted file mode 100644
index f180546..0000000
--- a/src/backend/taler-merchant-httpd_tip-reserve-helper.h
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2018--2019 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-reserve-helper.h
- * @brief helper functions to check the status of a tipping reserve
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_TIP_RESERVE_HELPER_H
-#define TALER_MERCHANT_HTTPD_TIP_RESERVE_HELPER_H
-#include <jansson.h>
-#include <taler/taler_util.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_tip-reserve-helper.h"
-
-
-/**
- * Context with input, output and internal state for
- * #TMH_check_tip_reserve() and #TMH_check_tip_reserve_cleanup().
- */
-struct TMH_CheckTipReserve
-{
-  /**
-   * Input: MHD connection we should resume when finished
-   */
-  struct MHD_Connection *connection;
-
-  /**
-   * Input: private key of the reserve.
-   */
-  struct TALER_ReservePrivateKeyP reserve_priv;
-
-  /**
-   * Output: Set to delay after which the reserve will expire if idle.
-   */
-  struct GNUNET_TIME_Relative idle_reserve_expiration_time;
-
-  /**
-   * Internal: exchange find operation.
-   */
-  struct TMH_EXCHANGES_FindOperation *fo;
-
-  /**
-   * Internal: reserve status operation.
-   */
-  struct TALER_EXCHANGE_ReservesGetHandle *rsh;
-
-  /**
-   * Internal: DLL for resumption on shutdown.
-   */
-  struct TMH_CheckTipReserve *next;
-
-  /**
-   * Internal: DLL for resumption on shutdown.
-   */
-  struct TMH_CheckTipReserve *prev;
-
-  /**
-   * Output: response object to return (on error only)
-   */
-  struct MHD_Response *response;
-
-  /**
-   * Output: Total amount deposited into the reserve.
-   */
-  struct TALER_Amount amount_deposited;
-
-  /**
-   * Output: total tip amount requested.
-   */
-  struct TALER_Amount amount_withdrawn;
-
-  /**
-   * Input: total amount authorized.
-   */
-  struct TALER_Amount amount_authorized;
-
-  /**
-   * Output: set to the time when the reserve will expire
-   */
-  struct GNUNET_TIME_Absolute reserve_expiration;
-
-  /**
-   * Output: HTTP status code to return (on error only)
-   */
-  unsigned int response_code;
-
-  /**
-   * Input: Set to #GNUNET_NO if no tips were authorized yet.
-   * Used to know that @e amount_authorized is not yet initialized
-   * and in that case the helper will set it to zero (once we know
-   * the currency).
-   */
-  int none_authorized;
-
-  /**
-   * Internal: Is the @e connection currently suspended?
-   * #GNUNET_NO if the @e connection was not suspended,
-   * #GNUNET_YES if the @e connection was suspended,
-   * #GNUNET_SYSERR if @e connection was resumed to as
-   * part of #MH_force_pc_resume during shutdown.
-   */
-  int suspended;
-
-};
-
-
-/**
- * Check the status of the given reserve at the given exchange.
- * Suspends the MHD connection while this is happening and resumes
- * processing once we know the reserve status (or once an error
- * code has been determined).
- *
- * @param[in,out] ctr context for checking the reserve status
- * @param tip_exchange the URL of the exchange to query
- */
-void
-TMH_check_tip_reserve (struct TMH_CheckTipReserve *ctr,
-                       const char *tip_exchange);
-
-
-/**
- * Clean up any state that might be left in @a ctr.
- *
- * @param[in] context to clean up
- */
-void
-TMH_check_tip_reserve_cleanup (struct TMH_CheckTipReserve *ctr);
-
-/**
- * Force all tip reserve helper contexts to be resumed as we are about to shut
- * down MHD.
- */
-void
-MH_force_trh_resume (void);
-
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_track-transfer.c 
b/src/backend/taler-merchant-httpd_track-transfer.c
deleted file mode 100644
index 7f55c91..0000000
--- a/src/backend/taler-merchant-httpd_track-transfer.c
+++ /dev/null
@@ -1,1089 +0,0 @@
-/*
-  This file is part of TALER
-  (C) 2014-2020 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_track-transfer.c
- * @brief implement API for tracking transfers and wire transfers
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_json_lib.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_auditors.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_track-transfer.h"
-
-
-/**
- * How long to wait before giving up processing with the exchange?
- */
-#define TRACK_TIMEOUT (GNUNET_TIME_relative_multiply 
(GNUNET_TIME_UNIT_SECONDS, \
-                                                      30))
-
-/**
- * How often do we retry the simple INSERT database transaction?
- */
-#define MAX_RETRIES 3
-
-/**
- * Context used for handing /track/transfer requests.
- */
-struct TrackTransferContext
-{
-
-  /**
-   * This MUST be first!
-   */
-  struct TM_HandlerContext hc;
-
-  /**
-   * Handle to the exchange.
-   */
-  struct TALER_EXCHANGE_Handle *eh;
-
-  /**
-   * Handle for the /wire/transfers request.
-   */
-  struct TALER_EXCHANGE_TransfersGetHandle *wdh;
-
-  /**
-   * For which merchant instance is this tracking request?
-   */
-  struct MerchantInstance *mi;
-
-  /**
-   * HTTP connection we are handling.
-   */
-  struct MHD_Connection *connection;
-
-  /**
-   * Response to return upon resume.
-   */
-  struct MHD_Response *response;
-
-  /**
-   * Handle for operation to lookup /keys (and auditors) from
-   * the exchange used for this transaction; NULL if no operation is
-   * pending.
-   */
-  struct TMH_EXCHANGES_FindOperation *fo;
-
-  /**
-   * Task run on timeout.
-   */
-  struct GNUNET_SCHEDULER_Task *timeout_task;
-
-  /**
-   * URL of the exchange.
-   */
-  char *url;
-
-  /**
-   * Wire method used for the transfer.
-   */
-  char *wire_method;
-
-  /**
-   * Pointer to the detail that we are currently
-   * checking in #check_transfer().
-   */
-  const struct TALER_TrackTransferDetails *current_detail;
-
-  /**
-   * Argument for the /wire/transfers request.
-   */
-  struct TALER_WireTransferIdentifierRawP wtid;
-
-  /**
-   * Full original response we are currently processing.
-   */
-  const json_t *original_response;
-
-  /**
-   * Modified response to return to the frontend.
-   */
-  json_t *deposits_response;
-
-  /**
-   * Which transaction detail are we currently looking at?
-   */
-  unsigned int current_offset;
-
-  /**
-   * Response code to return.
-   */
-  unsigned int response_code;
-
-  /**
-   * #GNUNET_NO if we did not find a matching coin.
-   * #GNUNET_SYSERR if we found a matching coin, but the amounts do not match.
-   * #GNUNET_OK if we did find a matching coin.
-   */
-  int check_transfer_result;
-};
-
-
-/**
- * Represents an entry in the table used to sum up
- * individual deposits for each h_contract_terms.
- */
-struct Entry
-{
-
-  /**
-   * Sum accumulator for deposited value.
-   */
-  struct TALER_Amount deposit_value;
-
-  /**
-   * Sum accumulator for deposit fee.
-   */
-  struct TALER_Amount deposit_fee;
-
-};
-
-
-/**
- * Free the @a rctx.
- *
- * @param rctx data to free
- */
-static void
-free_transfer_track_context (struct TrackTransferContext *rctx)
-{
-  if (NULL != rctx->fo)
-  {
-    TMH_EXCHANGES_find_exchange_cancel (rctx->fo);
-    rctx->fo = NULL;
-  }
-  if (NULL != rctx->timeout_task)
-  {
-    GNUNET_SCHEDULER_cancel (rctx->timeout_task);
-    rctx->timeout_task = NULL;
-  }
-  if (NULL != rctx->wdh)
-  {
-    TALER_EXCHANGE_transfers_get_cancel (rctx->wdh);
-    rctx->wdh = NULL;
-  }
-  if (NULL != rctx->url)
-  {
-    GNUNET_free (rctx->url);
-    rctx->url = NULL;
-  }
-  if (NULL != rctx->wire_method)
-  {
-    GNUNET_free (rctx->wire_method);
-    rctx->wire_method = NULL;
-  }
-  GNUNET_free (rctx);
-}
-
-
-/**
- * Callback that frees all the elements in the hashmap
- *
- * @param cls closure, NULL
- * @param key current key
- * @param value a `struct Entry`
- * @return #GNUNET_YES if the iteration should continue,
- *         #GNUNET_NO otherwise.
- */
-static int
-hashmap_free (void *cls,
-              const struct GNUNET_HashCode *key,
-              void *value)
-{
-  struct TALER_Entry *entry = value;
-
-  (void) cls;
-  (void) key;
-  GNUNET_free (entry);
-  return GNUNET_YES;
-}
-
-
-/**
- * Builds JSON response containing the summed-up amounts
- * from individual deposits.
- *
- * @param cls closure
- * @param key map's current key
- * @param map's current value
- * @return #GNUNET_YES if iteration is to be continued,
- *         #GNUNET_NO otherwise.
- */
-static int
-build_deposits_response (void *cls,
-                         const struct GNUNET_HashCode *key,
-                         void *value)
-{
-  struct TrackTransferContext *rctx = cls;
-  struct Entry *entry = value;
-  json_t *element;
-  json_t *contract_terms;
-  json_t *order_id;
-
-  db->preflight (db->cls);
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-      db->find_contract_terms_from_hash (db->cls,
-                                         &contract_terms,
-                                         key,
-                                         &rctx->mi->pubkey))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_NO;
-  }
-
-  order_id = json_object_get (contract_terms,
-                              "order_id");
-  if (NULL == order_id)
-  {
-    GNUNET_break_op (0);
-    json_decref (contract_terms);
-    return GNUNET_NO;
-  }
-  element = json_pack ("{s:O, s:o, s:o}",
-                       "order_id", order_id,
-                       "deposit_value", TALER_JSON_from_amount (
-                         &entry->deposit_value),
-                       "deposit_fee", TALER_JSON_from_amount (
-                         &entry->deposit_fee));
-  json_decref (contract_terms);
-  if (NULL == element)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_NO;
-  }
-  GNUNET_break (0 ==
-                json_array_append_new (rctx->deposits_response,
-                                       element));
-  return GNUNET_YES;
-}
-
-
-/**
- * Transform /track/transfer result as gotten from the exchange
- * and transforms it in a format liked by the backoffice Web interface.
- *
- * @param result response from exchange's /track/transfer
- * @result pointer to new JSON, or NULL upon errors.
- */
-static json_t *
-transform_response (const json_t *result,
-                    struct TrackTransferContext *rctx)
-{
-  json_t *deposits;
-  json_t *value;
-  json_t *result_mod = NULL;
-  size_t index;
-  const char *key;
-  struct GNUNET_HashCode h_key;
-  struct GNUNET_CONTAINER_MultiHashMap *map;
-  struct TALER_Amount iter_value;
-  struct TALER_Amount iter_fee;
-  struct Entry *current_entry;
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount ("deposit_value", &iter_value),
-    TALER_JSON_spec_amount ("deposit_fee", &iter_fee),
-    GNUNET_JSON_spec_string ("h_contract_terms", &key),
-    GNUNET_JSON_spec_end ()
-  };
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Transforming /track/transfer response.\n");
-  map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
-  deposits = json_object_get (result,
-                              "deposits");
-
-  json_array_foreach (deposits, index, value)
-  {
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (value,
-                           spec,
-                           NULL,
-                           NULL))
-    {
-      GNUNET_break_op (0);
-      return NULL;
-    }
-    GNUNET_CRYPTO_hash_from_string (key,
-                                    &h_key);
-
-    if (NULL != (current_entry =
-                   GNUNET_CONTAINER_multihashmap_get (map,
-                                                      &h_key)))
-    {
-      /* The map already knows this h_contract_terms*/
-      if ( (0 >
-            TALER_amount_add (&current_entry->deposit_value,
-                              &current_entry->deposit_value,
-                              &iter_value)) ||
-           (0 >
-            TALER_amount_add (&current_entry->deposit_fee,
-                              &current_entry->deposit_fee,
-                              &iter_fee)) )
-      {
-        GNUNET_JSON_parse_free (spec);
-        goto cleanup;
-      }
-    }
-    else
-    {
-      /* First time in the map for this h_contract_terms*/
-      current_entry = GNUNET_new (struct Entry);
-      current_entry->deposit_value = iter_value;
-      current_entry->deposit_fee = iter_fee;
-
-      if (GNUNET_SYSERR ==
-          GNUNET_CONTAINER_multihashmap_put (map,
-                                             &h_key,
-                                             current_entry,
-                                             
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
-      {
-        GNUNET_JSON_parse_free (spec);
-        goto cleanup;
-      }
-    }
-    GNUNET_JSON_parse_free (spec);
-  }
-  rctx->deposits_response = json_array ();
-
-  if (GNUNET_SYSERR ==
-      GNUNET_CONTAINER_multihashmap_iterate (map,
-                                             &build_deposits_response,
-                                             rctx))
-    goto cleanup;
-
-  result_mod = json_copy ((struct json_t *) result);
-  json_object_del (result_mod,
-                   "deposits");
-  json_object_set_new (result_mod,
-                       "deposits_sums",
-                       rctx->deposits_response);
-  rctx->deposits_response = NULL;
-cleanup:
-  GNUNET_CONTAINER_multihashmap_iterate (map,
-                                         &hashmap_free,
-                                         NULL);
-  GNUNET_CONTAINER_multihashmap_destroy (map);
-  return result_mod;
-}
-
-
-/**
- * Resume the given /track/transfer operation and send the given response.
- * Stores the response in the @a rctx and signals MHD to resume
- * the connection.  Also ensures MHD runs immediately.
- *
- * @param rctx transfer tracking context
- * @param response_code response code to use
- * @param response response data to send back
- */
-static void
-resume_track_transfer_with_response (struct TrackTransferContext *rctx,
-                                     unsigned int response_code,
-                                     struct MHD_Response *response)
-{
-  rctx->response_code = response_code;
-  rctx->response = response;
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Resuming /track/transfer handling as exchange interaction is 
done (%u)\n",
-              response_code);
-  if (NULL != rctx->timeout_task)
-  {
-    GNUNET_SCHEDULER_cancel (rctx->timeout_task);
-    rctx->timeout_task = NULL;
-  }
-  MHD_resume_connection (rctx->connection);
-  TMH_trigger_daemon (); /* we resumed, kick MHD */
-}
-
-
-/**
- * Custom cleanup routine for a `struct TrackTransferContext`.
- *
- * @param hc the `struct TrackTransferContext` to clean up.
- */
-static void
-track_transfer_cleanup (struct TM_HandlerContext *hc)
-{
-  struct TrackTransferContext *rctx = (struct TrackTransferContext *) hc;
-
-  free_transfer_track_context (rctx);
-}
-
-
-/**
- * This function checks that the information about the coin which
- * was paid back by _this_ wire transfer matches what _we_ (the merchant)
- * knew about this coin.
- *
- * @param cls closure with our `struct TrackTransferContext *`
- * @param transaction_id of the contract
- * @param coin_pub public key of the coin
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will transfer for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param refund_fee fee the exchange will charge for refunding this coin
- * @param exchange_proof proof from exchange that coin was accepted
- */
-static void
-check_transfer (void *cls,
-                const struct GNUNET_HashCode *h_contract_terms,
-                const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                const char *exchange_url,
-                const struct TALER_Amount *amount_with_fee,
-                const struct TALER_Amount *deposit_fee,
-                const struct TALER_Amount *refund_fee,
-                const struct TALER_Amount *wire_fee,
-                const json_t *exchange_proof)
-{
-  struct TrackTransferContext *rctx = cls;
-  const struct TALER_TrackTransferDetails *ttd = rctx->current_detail;
-
-  if (GNUNET_SYSERR == rctx->check_transfer_result)
-    return; /* already had a serious issue; odd that we're called more than 
once as well... */
-  if ( (0 != TALER_amount_cmp (amount_with_fee,
-                               &ttd->coin_value)) ||
-       (0 != TALER_amount_cmp (deposit_fee,
-                               &ttd->coin_fee)) )
-  {
-    /* Disagreement between the exchange and us about how much this
-       coin is worth! */
-    GNUNET_break_op (0);
-    rctx->check_transfer_result = GNUNET_SYSERR;
-    /* Build the `TrackTransferConflictDetails` */
-    rctx->response
-      = TALER_MHD_make_json_pack (
-          "{s:I, s:s, s:o, s:I, s:o, s:o, s:s, s:o, s:o}",
-          "code", (json_int_t) TALER_EC_TRACK_TRANSFER_CONFLICTING_REPORTS,
-          "hint", "disagreement about deposit valuation",
-          "exchange_deposit_proof", exchange_proof,
-          "conflict_offset", (json_int_t) rctx->current_offset,
-          "exchange_transfer_proof", rctx->original_response,
-          "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
-          "h_contract_terms", GNUNET_JSON_from_data_auto (
-            &ttd->h_contract_terms),
-          "amount_with_fee", TALER_JSON_from_amount (amount_with_fee),
-          "deposit_fee", TALER_JSON_from_amount (deposit_fee));
-    return;
-  }
-  rctx->check_transfer_result = GNUNET_OK;
-}
-
-
-/**
- * Check that the given @a wire_fee is what the
- * @a exchange_pub should charge at the @a execution_time.
- * If the fee is correct (according to our database),
- * return #GNUNET_OK.  If we do not have the fee structure
- * in our DB, we just accept it and return #GNUNET_NO;
- * if we have proof that the fee is bogus, we respond with
- * the proof to the client and return #GNUNET_SYSERR.
- *
- * @param rctx context of the transfer to respond to
- * @param json response from the exchange
- * @param execution_time time of the wire transfer
- * @param wire_fee fee claimed by the exchange
- * @return #GNUNET_SYSERR if we returned hard proof of
- *   missbehavior from the exchange to the client
- */
-static int
-check_wire_fee (struct TrackTransferContext *rctx,
-                const json_t *json,
-                struct GNUNET_TIME_Absolute execution_time,
-                const struct TALER_Amount *wire_fee)
-{
-  const struct TALER_MasterPublicKeyP *master_pub;
-  struct GNUNET_HashCode h_wire_method;
-  struct TALER_Amount expected_fee;
-  struct TALER_Amount closing_fee;
-  struct TALER_MasterSignatureP master_sig;
-  struct GNUNET_TIME_Absolute start_date;
-  struct GNUNET_TIME_Absolute end_date;
-  enum GNUNET_DB_QueryStatus qs;
-  const struct TALER_EXCHANGE_Keys *keys;
-
-  keys = TALER_EXCHANGE_get_keys (rctx->eh);
-  if (NULL == keys)
-  {
-    GNUNET_break (0);
-    return GNUNET_NO;
-  }
-  master_pub = &keys->master_pub;
-  GNUNET_CRYPTO_hash (rctx->wire_method,
-                      strlen (rctx->wire_method) + 1,
-                      &h_wire_method);
-  db->preflight (db->cls);
-  qs = db->lookup_wire_fee (db->cls,
-                            master_pub,
-                            &h_wire_method,
-                            execution_time,
-                            &expected_fee,
-                            &closing_fee,
-                            &start_date,
-                            &end_date,
-                            &master_sig);
-  if (0 >= qs)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Failed to find wire fee for `%s' and method `%s' at %s in DB, 
accepting blindly that the fee is %s\n",
-                TALER_B2S (master_pub),
-                rctx->wire_method,
-                GNUNET_STRINGS_absolute_time_to_string (execution_time),
-                TALER_amount2s (wire_fee));
-    return GNUNET_NO;
-  }
-  if (0 <= TALER_amount_cmp (&expected_fee,
-                             wire_fee))
-    return GNUNET_OK; /* expected_fee >= wire_fee */
-
-  /* Wire fee check failed, export proof to client */
-  resume_track_transfer_with_response (
-    rctx,
-    MHD_HTTP_FAILED_DEPENDENCY,
-    TALER_MHD_make_json_pack (
-      "{s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:O}",
-      "code", (json_int_t) TALER_EC_TRACK_TRANSFER_JSON_BAD_WIRE_FEE,
-      "wire_fee", TALER_JSON_from_amount (wire_fee),
-      "execution_time", GNUNET_JSON_from_time_abs (execution_time),
-      "expected_wire_fee", TALER_JSON_from_amount (&expected_fee),
-      "expected_closing_fee", TALER_JSON_from_amount (&closing_fee),
-      "start_date", GNUNET_JSON_from_time_abs (start_date),
-      "end_date", GNUNET_JSON_from_time_abs (end_date),
-      "master_sig", GNUNET_JSON_from_data_auto (&master_sig),
-      "master_pub", GNUNET_JSON_from_data_auto (master_pub),
-      "json", json));
-  return GNUNET_SYSERR;
-}
-
-
-/**
- * Function called with detailed wire transfer data, including all
- * of the coin transactions that were combined into the wire transfer.
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param exchange_pub public key of the exchange used to sign @a json
- * @param h_wire hash of the wire transfer address the transfer went to, or 
NULL on error
- * @param execution_time time when the exchange claims to have performed the 
wire transfer
- * @param total_amount total amount of the wire transfer, or NULL if the 
exchange could
- *             not provide any @a wtid (set only if @a http_status is 
#MHD_HTTP_OK)
- * @param wire_fee wire fee that was charged by the exchange
- * @param details_length length of the @a details array
- * @param details array with details about the combined transactions
- */
-static void
-wire_transfer_cb (void *cls,
-                  const struct TALER_EXCHANGE_HttpResponse *hr,
-                  const struct TALER_ExchangePublicKeyP *exchange_pub,
-                  const struct GNUNET_HashCode *h_wire,
-                  struct GNUNET_TIME_Absolute execution_time,
-                  const struct TALER_Amount *total_amount,
-                  const struct TALER_Amount *wire_fee,
-                  unsigned int details_length,
-                  const struct TALER_TrackTransferDetails *details)
-{
-  struct TrackTransferContext *rctx = cls;
-  json_t *jresponse;
-  enum GNUNET_DB_QueryStatus qs;
-
-  rctx->wdh = NULL;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Got response code %u from exchange for /track/transfer\n",
-              hr->http_status);
-  if (MHD_HTTP_OK != hr->http_status)
-  {
-    resume_track_transfer_with_response (
-      rctx,
-      MHD_HTTP_FAILED_DEPENDENCY,
-      TALER_MHD_make_json_pack (
-        "{s:I, s:I, s:I, s:O}",
-        "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_ERROR,
-        "exchange_code", (json_int_t) hr->ec,
-        "exchange_http_status", (json_int_t) hr->http_status,
-        "exchange_reply", hr->reply));
-    return;
-  }
-  for (unsigned int i = 0; i<MAX_RETRIES; i++)
-  {
-    db->preflight (db->cls);
-    qs = db->store_transfer_to_proof (db->cls,
-                                      rctx->url,
-                                      &rctx->wtid,
-                                      execution_time,
-                                      exchange_pub,
-                                      hr->reply);
-    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-      break;
-  }
-  if (0 > qs)
-  {
-    /* Special report if retries insufficient */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    resume_track_transfer_with_response
-      (rctx,
-      MHD_HTTP_INTERNAL_SERVER_ERROR,
-      TALER_MHD_make_json_pack ("{s:I, s:s}",
-                                "code",
-                                (json_int_t)
-                                
TALER_EC_TRACK_TRANSFER_DB_STORE_TRANSFER_ERROR,
-                                "details",
-                                "failed to store response from exchange to 
local database"));
-    return;
-  }
-  rctx->original_response = hr->reply;
-
-  if (GNUNET_SYSERR ==
-      check_wire_fee (rctx,
-                      hr->reply,
-                      execution_time,
-                      wire_fee))
-    return;
-
-  /* Now we want to double-check that any (Taler coin) deposit
-   * which is accounted into _this_ wire transfer, does exist
-   * into _our_ database.  This is the rationale: if the
-   * exchange paid us for it, we must have received it _beforehands_!
-   *
-   * details_length is how many (Taler coin) deposits have been
-   * aggregated into _this_ wire transfer.
-   *///
-  for (unsigned int i = 0; i<details_length; i++)
-  {
-    rctx->current_offset = i;
-    rctx->current_detail = &details[i];
-    /* Set the coin as "never seen" before. */
-    rctx->check_transfer_result = GNUNET_NO;
-    db->preflight (db->cls);
-    qs = db->find_payments_by_hash_and_coin (db->cls,
-                                             &details[i].h_contract_terms,
-                                             &rctx->mi->pubkey,
-                                             &details[i].coin_pub,
-                                             &check_transfer,
-                                             rctx);
-    if (0 > qs)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_track_transfer_with_response
-        (rctx,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_MHD_make_json_pack ("{s:I, s:s}",
-                                  "code",
-                                  (json_int_t)
-                                  
TALER_EC_TRACK_TRANSFER_DB_FETCH_DEPOSIT_ERROR,
-                                  "details",
-                                  "failed to obtain deposit data from local 
database"));
-      return;
-    }
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-    {
-      /* The exchange says we made this deposit, but WE do not
-         recall making it (corrupted / unreliable database?)!
-         Well, let's say thanks and accept the money! */
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Failed to find payment data in DB\n");
-      rctx->check_transfer_result = GNUNET_OK;
-    }
-    if (GNUNET_NO == rctx->check_transfer_result)
-    {
-      /* Internal error: how can we have called #check_transfer()
-         but still have no result? */
-      GNUNET_break (0);
-      resume_track_transfer_with_response
-        (rctx,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_MHD_make_json_pack ("{s:I, s:s, s:I, s:s}",
-                                  "code",
-                                  (json_int_t)
-                                  
TALER_EC_TRACK_TRANSFER_DB_INTERNAL_LOGIC_ERROR,
-                                  "details", "internal logic error",
-                                  "line", (json_int_t) __LINE__,
-                                  "file", __FILE__));
-      return;
-    }
-    if (GNUNET_SYSERR == rctx->check_transfer_result)
-    {
-      /* #check_transfer() failed, report conflict! */
-      GNUNET_break_op (0);
-      GNUNET_assert (NULL != rctx->response);
-      resume_track_transfer_with_response
-        (rctx,
-        MHD_HTTP_FAILED_DEPENDENCY,
-        rctx->response);
-      rctx->response = NULL;
-      return;
-    }
-    /* Response is consistent with the /deposit we made,
-       remember it for future reference */
-    for (unsigned int r = 0; r<MAX_RETRIES; r++)
-    {
-      db->preflight (db->cls);
-      qs = db->store_coin_to_transfer (db->cls,
-                                       &details[i].h_contract_terms,
-                                       &details[i].coin_pub,
-                                       &rctx->wtid);
-      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-        break;
-    }
-    if (0 > qs)
-    {
-      /* Special report if retries insufficient */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_track_transfer_with_response
-        (rctx,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_MHD_make_json_pack ("{s:I, s:s}",
-                                  "code",
-                                  (json_int_t)
-                                  TALER_EC_TRACK_TRANSFER_DB_STORE_COIN_ERROR,
-                                  "details",
-                                  "failed to store response from exchange to 
local database"));
-      return;
-    }
-  }
-  rctx->original_response = NULL;
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "About to call tracks transformator.\n");
-
-  if (NULL == (jresponse =
-                 transform_response (hr->reply,
-                                     rctx)))
-  {
-    resume_track_transfer_with_response
-      (rctx,
-      MHD_HTTP_INTERNAL_SERVER_ERROR,
-      TALER_MHD_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
-                            "Fail to elaborate the response."));
-    return;
-  }
-
-  resume_track_transfer_with_response (rctx,
-                                       MHD_HTTP_OK,
-                                       TALER_MHD_make_json (jresponse));
-  json_decref (jresponse);
-}
-
-
-/**
- * Function called with the result of our exchange lookup.
- *
- * @param cls the `struct TrackTransferContext`
- * @param hr HTTP response details
- * @param eh NULL if exchange was not found to be acceptable
- * @param wire_fee NULL (we did not specify a wire method)
- * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
- */
-static void
-process_track_transfer_with_exchange (void *cls,
-                                      const struct
-                                      TALER_EXCHANGE_HttpResponse *hr,
-                                      struct TALER_EXCHANGE_Handle *eh,
-                                      const struct TALER_Amount *wire_fee,
-                                      int exchange_trusted)
-{
-  struct TrackTransferContext *rctx = cls;
-
-  rctx->fo = NULL;
-  if (MHD_HTTP_OK != hr->http_status)
-  {
-    /* The request failed somehow */
-    GNUNET_break_op (0);
-    resume_track_transfer_with_response (
-      rctx,
-      MHD_HTTP_FAILED_DEPENDENCY,
-      TALER_MHD_make_json_pack (
-        (NULL != hr->reply)
-        ? "{s:s, s:I, s:I, s:I, s:O}"
-        : "{s:s, s:I, s:I, s:I}",
-        "hint", "failed to obtain meta-data from exchange",
-        "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_KEYS_FAILURE,
-        "exchange_http_status", (json_int_t) hr->http_status,
-        "exchange_code", (json_int_t) hr->ec,
-        "exchange_reply", hr->reply));
-    return;
-  }
-  rctx->eh = eh;
-  rctx->wdh = TALER_EXCHANGE_transfers_get (eh,
-                                            &rctx->wtid,
-                                            &wire_transfer_cb,
-                                            rctx);
-  if (NULL == rctx->wdh)
-  {
-    GNUNET_break (0);
-    resume_track_transfer_with_response
-      (rctx,
-      MHD_HTTP_INTERNAL_SERVER_ERROR,
-      TALER_MHD_make_json_pack ("{s:I, s:s}",
-                                "code",
-                                (json_int_t)
-                                TALER_EC_TRACK_TRANSFER_REQUEST_ERROR,
-                                "error",
-                                "failed to run /transfers/ GET on exchange"));
-  }
-}
-
-
-/**
- * Handle a timeout for the processing of the track transfer request.
- *
- * @param cls closure
- */
-static void
-handle_track_transfer_timeout (void *cls)
-{
-  struct TrackTransferContext *rctx = cls;
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Resuming /track/transfer with error after timeout\n");
-  rctx->timeout_task = NULL;
-
-  if (NULL != rctx->fo)
-  {
-    TMH_EXCHANGES_find_exchange_cancel (rctx->fo);
-    rctx->fo = NULL;
-  }
-  resume_track_transfer_with_response (rctx,
-                                       MHD_HTTP_SERVICE_UNAVAILABLE,
-                                       TALER_MHD_make_error (
-                                         
TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT,
-                                         "exchange not reachable"));
-}
-
-
-/**
- * Function called with information about a wire transfer identifier.
- * Generate a response based on the given @a proof.
- *
- * @param cls closure
- * @param proof proof from exchange about what the wire transfer was for.
- *              should match the `TrackTransactionResponse` format
- *              of the exchange
- */
-static void
-proof_cb (void *cls,
-          const json_t *proof)
-{
-  struct TrackTransferContext *rctx = cls;
-  json_t *transformed_response;
-
-  if (NULL == (transformed_response =
-                 transform_response (proof,
-                                     rctx)))
-  {
-    rctx->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
-    rctx->response
-      = TALER_MHD_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
-                              "Fail to elaborate response.");
-    return;
-  }
-
-  rctx->response_code = MHD_HTTP_OK;
-  rctx->response = TALER_MHD_make_json (transformed_response);
-  json_decref (transformed_response);
-}
-
-
-/**
- * Manages a /track/transfer call, thus it calls the /track/wtid
- * offered by the exchange in order to return the set of transfers
- * (of coins) associated with a given wire transfer.
- *
- * @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
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_track_transfer (struct TMH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size,
-                           struct MerchantInstance *mi)
-{
-  struct TrackTransferContext *rctx;
-  const char *str;
-  const char *url;
-  const char *wire_method;
-  MHD_RESULT ret;
-  enum GNUNET_DB_QueryStatus qs;
-
-  if (NULL == *connection_cls)
-  {
-    rctx = GNUNET_new (struct TrackTransferContext);
-    rctx->hc.cc = &track_transfer_cleanup;
-    rctx->connection = connection;
-    *connection_cls = rctx;
-  }
-  else
-  {
-    /* not first call, recover state */
-    rctx = *connection_cls;
-  }
-
-  if (0 != rctx->response_code)
-  {
-    /* We are *done* processing the request, just queue the response (!) */
-    if (UINT_MAX == rctx->response_code)
-    {
-      GNUNET_break (0);
-      return MHD_NO; /* hard error */
-    }
-    ret = MHD_queue_response (connection,
-                              rctx->response_code,
-                              rctx->response);
-    if (NULL != rctx->response)
-    {
-      MHD_destroy_response (rctx->response);
-      rctx->response = NULL;
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Queueing response (%u) for /track/transfer (%s).\n",
-                (unsigned int) rctx->response_code,
-                ret ? "OK" : "FAILED");
-    return ret;
-  }
-  if ( (NULL != rctx->fo) ||
-       (NULL != rctx->eh) )
-  {
-    /* likely old MHD version */
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Not sure why we are here, should be suspended\n");
-    return MHD_YES; /* still work in progress */
-  }
-
-  url = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "exchange");
-  if (NULL == url)
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MISSING,
-                                       "exchange");
-  rctx->url = GNUNET_strdup (url);
-
-  /* FIXME: change again: we probably don't want the wire_method
-     but rather the _account_ (section) here! */
-  wire_method = MHD_lookup_connection_value (connection,
-                                             MHD_GET_ARGUMENT_KIND,
-                                             "wire_method");
-  if (NULL == wire_method)
-  {
-    if (1)
-    {
-      /* temporary work-around until demo is adjusted... */
-      GNUNET_break (0);
-      wire_method = "x-taler-bank";
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Client needs fixing, see API change for #4943!\n");
-    }
-    else
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         TALER_EC_PARAMETER_MISSING,
-                                         "wire_method");
-  }
-  rctx->wire_method = GNUNET_strdup (wire_method);
-  rctx->mi = mi;
-  str = MHD_lookup_connection_value (connection,
-                                     MHD_GET_ARGUMENT_KIND,
-                                     "wtid");
-  if (NULL == str)
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MISSING,
-                                       "wtid");
-  if (GNUNET_OK !=
-      GNUNET_STRINGS_string_to_data (str,
-                                     strlen (str),
-                                     &rctx->wtid,
-                                     sizeof (rctx->wtid)))
-  {
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MALFORMED,
-                                       "wtid");
-  }
-
-  /* Check if reply is already in database! */
-  db->preflight (db->cls);
-  qs = db->find_proof_by_wtid (db->cls,
-                               rctx->url,
-                               &rctx->wtid,
-                               &proof_cb,
-                               rctx);
-  if (0 > qs)
-  {
-    /* Simple select queries should not cause serialization issues */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_TRACK_TRANSFER_DB_FETCH_DEPOSIT_ERROR,
-                                       "Fail to query database about proofs");
-  }
-  if (0 != rctx->response_code)
-  {
-    ret = MHD_queue_response (connection,
-                              rctx->response_code,
-                              rctx->response);
-    if (NULL != rctx->response)
-    {
-      MHD_destroy_response (rctx->response);
-      rctx->response = NULL;
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Queueing response (%u) for /track/transfer (%s).\n",
-                (unsigned int) rctx->response_code,
-                ret ? "OK" : "FAILED");
-    return ret;
-  }
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Suspending /track/transfer handling while working with the 
exchange\n");
-  MHD_suspend_connection (connection);
-  rctx->fo = TMH_EXCHANGES_find_exchange (url,
-                                          NULL,
-                                          GNUNET_NO,
-                                          
&process_track_transfer_with_exchange,
-                                          rctx);
-  rctx->timeout_task
-    = GNUNET_SCHEDULER_add_delayed (TRACK_TIMEOUT,
-                                    &handle_track_transfer_timeout,
-                                    rctx);
-  return MHD_YES;
-}
-
-
-/* end of taler-merchant-httpd_track-transfer.c */
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index 3edc450..d7eca44 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -54,6 +54,7 @@ libtaler_plugin_merchantdb_postgres_la_LDFLAGS = \
   -ltalerpq \
   -ltalerutil \
   -ltalerjson \
+  -ltalermhd \
   -lpq \
   -lgnunetutil $(XLIB)
 
@@ -68,9 +69,10 @@ test_merchantdb_postgres_SOURCES = \
   test_merchantdb.c
 
 test_merchantdb_postgres_LDFLAGS = \
-  -lgnunetutil \
   -ltalerutil \
   -ltalerjson \
+  -lgnunetjson \
+  -lgnunetutil \
   -ljansson
 
 test_merchantdb_postgres_LDADD = \
diff --git a/src/backenddb/drop0001.sql b/src/backenddb/drop0001.sql
index 4a1b3ec..aec588d 100644
--- a/src/backenddb/drop0001.sql
+++ b/src/backenddb/drop0001.sql
@@ -25,22 +25,34 @@ BEGIN;
 
 -- Drops for 0001.sql
 
-DROP TABLE IF EXISTS merchant_transfers CASCADE;
-DROP TABLE IF EXISTS merchant_deposits CASCADE;
-DROP TABLE IF EXISTS merchant_transactions CASCADE;
-DROP TABLE IF EXISTS merchant_proofs CASCADE;
+DROP TABLE IF EXISTS merchant_exchange_wire_fees CASCADE;
+DROP TABLE IF EXISTS merchant_exchange_signing_keys CASCADE;
+DROP TABLE IF EXISTS merchant_instances CASCADE;
+DROP TABLE IF EXISTS merchant_keys CASCADE;
+DROP TABLE IF EXISTS merchant_accounts CASCADE;
+DROP TABLE IF EXISTS merchant_inventory CASCADE;
+DROP TABLE IF EXISTS merchant_inventory_locks CASCADE;
+DROP TABLE IF EXISTS merchant_accounts CASCADE;
+DROP TABLE IF EXISTS merchant_orders CASCADE;
+DROP TABLE IF EXISTS merchant_order_locks CASCADE;
 DROP TABLE IF EXISTS merchant_contract_terms CASCADE;
+DROP TABLE IF EXISTS merchant_deposits CASCADE;
 DROP TABLE IF EXISTS merchant_refunds CASCADE;
-DROP TABLE IF EXISTS exchange_wire_fees CASCADE;
+DROP TABLE IF EXISTS merchant_refund_proofs CASCADE;
+DROP TABLE IF EXISTS merchant_credits CASCADE;
+DROP TABLE IF EXISTS merchant_transfers CASCADE;
+DROP TABLE IF EXISTS merchant_transfer_signatures CASCADE;
+DROP TABLE IF EXISTS merchant_transfer_by_coin CASCADE;
+DROP TABLE IF EXISTS merchant_transfer_to_coin CASCADE;
+DROP TABLE IF EXISTS merchant_deposit_to_transfer CASCADE;
+DROP TABLE IF EXISTS merchant_tip_reserves CASCADE;
+DROP TABLE IF EXISTS merchant_tip_reserve_keys CASCADE;
 DROP TABLE IF EXISTS merchant_tips CASCADE;
 DROP TABLE IF EXISTS merchant_tip_pickups CASCADE;
-DROP TABLE IF EXISTS merchant_tip_reserve_credits CASCADE;
-DROP TABLE IF EXISTS merchant_tip_reserves CASCADE;
-DROP TABLE IF EXISTS merchant_orders CASCADE;
-DROP TABLE IF EXISTS merchant_session_info CASCADE;
+DROP TABLE IF EXISTS merchant_tip_pickup_signatures CASCADE;
 
--- Drop versioning (0000.sql)
-DROP SCHEMA IF EXISTS _v CASCADE;
+-- Unregister patch (0001.sql)
+SELECT _v.unregister_patch('merchant-0001');
 
 -- And we're out of here...
 COMMIT;
diff --git a/src/backenddb/merchant-0000.sql b/src/backenddb/merchant-0000.sql
index 116f409..98e7f66 100644
--- a/src/backenddb/merchant-0000.sql
+++ b/src/backenddb/merchant-0000.sql
@@ -205,7 +205,7 @@ BEGIN
     RETURN;
 END;
 $$ language plpgsql;
-COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to 
register patches in database. Raises exception if there are conflicts, 
prerequisites are not installed or the migration has already been installed.';
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[]) IS 'Function to 
register patches in database. Raises exception if there are conflicts, 
prerequisites are not installed or the migration has already been installed.';
 
 CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof 
INT4 AS $$
     SELECT _v.register_patch( $1, $2, NULL );
diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql
index 63eb4bf..f6f91f3 100644
--- a/src/backenddb/merchant-0001.sql
+++ b/src/backenddb/merchant-0001.sql
@@ -20,32 +20,266 @@ BEGIN;
 -- Check patch versioning is in place.
 SELECT _v.register_patch('merchant-0001', NULL, NULL);
 
+---------------- Exchange information ---------------------------
+
+CREATE TABLE IF NOT EXISTS merchant_exchange_wire_fees
+  (wirefee_serial BIGSERIAL PRIMARY KEY
+  ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)
+  ,h_wire_method BYTEA NOT NULL CHECK (LENGTH(h_wire_method)=64)
+  ,start_date INT8 NOT NULL
+  ,end_date INT8 NOT NULL
+  ,wire_fee_val INT8 NOT NULL
+  ,wire_fee_frac INT4 NOT NULL
+  ,closing_fee_val INT8 NOT NULL
+  ,closing_fee_frac INT4 NOT NULL
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  ,UNIQUE (master_pub,h_wire_method,start_date)
+  );
+COMMENT ON TABLE merchant_exchange_wire_fees
+ IS 'Here we store proofs of the wire fee structure of the various exchanges';
+COMMENT ON COLUMN merchant_exchange_wire_fees.master_pub
+ IS 'Master public key of the exchange with these wire fees';
+
+CREATE TABLE IF NOT EXISTS merchant_exchange_signing_keys
+  (signkey_serial BIGSERIAL PRIMARY KEY
+  ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)
+  ,exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32)
+  ,start_date INT8 NOT NULL
+  ,expire_date INT8 NOT NULL
+  ,end_date INT8 NOT NULL
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64),
+  UNIQUE (master_pub, exchange_pub, start_date)
+  );
+COMMENT ON TABLE merchant_exchange_signing_keys
+ IS 'Here we store proofs of the exchange online signing keys being signed by 
the exchange master key';
+COMMENT ON COLUMN merchant_exchange_signing_keys.master_pub
+ IS 'Master public key of the exchange with these online signing keys';
+
+
+-------------------------- Instances  ---------------------------
+
+CREATE TABLE IF NOT EXISTS merchant_instances
+  (merchant_serial BIGSERIAL PRIMARY KEY
+  ,merchant_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(merchant_pub)=32)
+  ,merchant_id VARCHAR NOT NULL UNIQUE
+  ,merchant_name VARCHAR NOT NULL
+  ,address BYTEA NOT NULL
+  ,jurisdiction BYTEA NOT NULL
+  ,default_max_deposit_fee_val INT8 NOT NULL
+  ,default_max_deposit_fee_frac INT4 NOT NULL
+  ,default_max_wire_fee_val INT8 NOT NULL
+  ,default_max_wire_fee_frac INT4 NOT NULL
+  ,default_wire_fee_amortization INT4 NOT NULL
+  ,default_wire_transfer_delay INT8 NOT NULL
+  ,default_pay_delay INT8 NOT NULL
+  );
+COMMENT ON TABLE merchant_instances
+  IS 'all the instances supported by this backend';
+COMMENT ON COLUMN merchant_instances.merchant_id
+  IS 'identifier of the merchant as used in the base URL (required)';
+COMMENT ON COLUMN merchant_instances.merchant_name
+  IS 'legal name of the merchant as a simple string (required)';
+COMMENT ON COLUMN merchant_instances.address
+  IS 'physical address of the merchant as a Location in JSON format 
(required)';
+COMMENT ON COLUMN merchant_instances.jurisdiction
+  IS 'jurisdiction of the merchant as a Location in JSON format (required)';
+
+CREATE TABLE IF NOT EXISTS merchant_keys
+  (merchant_priv BYTEA NOT NULL UNIQUE CHECK (LENGTH(merchant_priv)=32),
+   merchant_serial BIGINT PRIMARY KEY
+     REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+  );
+COMMENT ON TABLE merchant_keys
+  IS 'private keys of instances that have not been deleted';
+
+CREATE TABLE IF NOT EXISTS merchant_accounts
+  (account_serial BIGSERIAL PRIMARY KEY
+  ,merchant_serial BIGINT NOT NULL UNIQUE
+     REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+  ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64)
+  ,salt BYTEA NOT NULL CHECK (LENGTH(salt)=64)
+  ,payto_uri VARCHAR NOT NULL
+  ,active boolean NOT NULL
+  ,UNIQUE (merchant_serial,payto_uri)
+  );
+COMMENT ON TABLE merchant_accounts
+  IS 'bank accounts of the instances';
+COMMENT ON COLUMN merchant_accounts.h_wire
+  IS 'salted hash of payto_uri';
+COMMENT ON COLUMN merchant_accounts.salt
+  IS 'salt used when hashing payto_uri into h_wire';
+COMMENT ON COLUMN merchant_accounts.payto_uri
+  IS 'payto URI of a merchant bank account';
+COMMENT ON COLUMN merchant_accounts.active
+  IS 'true if we actively use this bank account, false if it is just kept 
around for older contracts to refer to';
+
+
+-------------------------- Inventory  ---------------------------
+
+CREATE TABLE IF NOT EXISTS merchant_inventory
+  (product_serial BIGSERIAL PRIMARY KEY
+  ,merchant_serial BIGINT NOT NULL
+    REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+  ,product_id VARCHAR NOT NULL
+  ,description VARCHAR NOT NULL
+  ,description_i18n BYTEA NOT NULL
+  ,unit VARCHAR NOT NULL
+  ,image BYTEA NOT NULL
+  ,taxes BYTEA NOT NULL
+  ,price_val INT8 NOT NULL
+  ,price_frac INT4 NOT NULL
+  ,total_stock BIGINT NOT NULL
+  ,total_sold BIGINT NOT NULL DEFAULT 0
+  ,total_lost BIGINT NOT NULL DEFAULT 0
+  ,address BYTEA NOT NULL
+  ,next_restock INT8 NOT NULL
+  ,UNIQUE (merchant_serial, product_id)
+  );
+COMMENT ON TABLE merchant_inventory
+  IS 'products offered by the merchant (may be incomplete, frontend can 
override)';
+COMMENT ON COLUMN merchant_inventory.description
+  IS 'Human-readable product description';
+COMMENT ON COLUMN merchant_inventory.description_i18n
+  IS 'JSON map from IETF BCP 47 language tags to localized descriptions';
+COMMENT ON COLUMN merchant_inventory.unit
+  IS 'Unit of sale for the product (liters, kilograms, packages)';
+COMMENT ON COLUMN merchant_inventory.image
+  IS 'NOT NULL, but can be 0 bytes; must contain an ImageDataUrl';
+COMMENT ON COLUMN merchant_inventory.taxes
+  IS 'JSON array containing taxes the merchant pays, must be JSON, but can be 
just "[]"';
+COMMENT ON COLUMN merchant_inventory.price_val
+  IS 'Current price of one unit of the product';
+COMMENT ON COLUMN merchant_inventory.total_stock
+  IS 'A value of -1 is used for unlimited (eletronic good), may never be 
lowered';
+COMMENT ON COLUMN merchant_inventory.total_sold
+  IS 'Number of products sold, must be below total_stock, non-negative, may 
never be lowered';
+COMMENT ON COLUMN merchant_inventory.total_lost
+  IS 'Number of products that used to be in stock but were lost (spoiled, 
damaged), may never be lowered; total_stock >= total_sold + total_lost must 
always hold';
+COMMENT ON COLUMN merchant_inventory.address
+  IS 'JSON formatted Location of where the product is stocked';
+COMMENT ON COLUMN merchant_inventory.next_restock
+  IS 'GNUnet absolute time indicating when the next restock is expected. 0 for 
unknown.';
+
+CREATE TABLE IF NOT EXISTS merchant_inventory_locks
+  (product_serial BIGINT NOT NULL
+     REFERENCES merchant_inventory (product_serial) -- NO "ON DELETE CASCADE": 
locks prevent deletion!
+  ,lock_uuid BYTEA NOT NULL CHECK (LENGTH(lock_uuid)=16)
+  ,total_locked BIGINT NOT NULL
+  ,expiration INT8 NOT NULL
+  );
+CREATE INDEX IF NOT EXISTS merchant_inventory_locks_by_expiration
+  ON merchant_inventory_locks
+    (expiration);
+CREATE INDEX IF NOT EXISTS merchant_inventory_locks_by_uuid
+  ON merchant_inventory_locks
+    (lock_uuid);
+COMMENT ON TABLE merchant_inventory_locks
+  IS 'locks on inventory helt by shopping carts; note that locks MAY not be 
honored if merchants increase total_lost for inventory';
+COMMENT ON COLUMN merchant_inventory_locks.total_locked
+  IS 'how many units of the product does this lock reserve';
+COMMENT ON COLUMN merchant_inventory_locks.expiration
+  IS 'when does this lock automatically expire (if no order is created)';
+
+
+---------------- Orders and contracts ---------------------------
 
 CREATE TABLE IF NOT EXISTS merchant_orders
-  (order_id VARCHAR NOT NULL
-  ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
+  (order_serial BIGSERIAL PRIMARY KEY
+  ,merchant_serial BIGINT NOT NULL
+    REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+  ,order_id VARCHAR NOT NULL
+  ,pay_deadline INT8 NOT NULL
+  ,creation_time INT8 NOT NULL
   ,contract_terms BYTEA NOT NULL
-  ,timestamp INT8 NOT NULL
-  ,PRIMARY KEY (order_id, merchant_pub)
+  ,UNIQUE (merchant_serial, order_id)
+  );
+COMMENT ON TABLE merchant_orders
+  IS 'Orders we offered to a customer, but that have not yet been claimed';
+COMMENT ON COLUMN merchant_orders.contract_terms
+  IS 'Claiming changes the contract_terms, hence we have no hash of the terms 
in this table';
+COMMENT ON COLUMN merchant_orders.merchant_serial
+  IS 'Identifies the instance offering the contract';
+COMMENT ON COLUMN merchant_orders.pay_deadline
+  IS 'How long is the offer valid. After this time, the order can be garbage 
collected';
+CREATE INDEX IF NOT EXISTS merchant_orders_by_expiration
+  ON merchant_orders
+    (pay_deadline);
+CREATE INDEX IF NOT EXISTS merchant_orders_by_creation_time
+  ON merchant_orders
+    (creation_time);
+
+CREATE TABLE IF NOT EXISTS merchant_order_locks
+  (product_serial BIGINT NOT NULL
+     REFERENCES merchant_inventory (product_serial) -- NO "ON DELETE CASCADE": 
locks prevent deletion!
+  ,total_locked BIGINT NOT NULL
+  ,order_serial BIGINT NOT NULL
+     REFERENCES merchant_orders (order_serial) ON DELETE CASCADE
   );
+CREATE INDEX IF NOT EXISTS merchant_orders_locks_by_order_and_product
+  ON merchant_order_locks
+    (order_serial, product_serial);
+COMMENT ON TABLE merchant_order_locks
+  IS 'locks on orders awaiting claim and payment; note that locks MAY not be 
honored if merchants increase total_lost for inventory';
+COMMENT ON COLUMN merchant_order_locks.total_locked
+  IS 'how many units of the product does this lock reserve';
 
--- Offers we made to customers
 CREATE TABLE IF NOT EXISTS merchant_contract_terms
-   (order_id VARCHAR NOT NULL
-   ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
-   ,contract_terms BYTEA NOT NULL
-   ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
-   ,timestamp INT8 NOT NULL
-   ,row_id BIGSERIAL UNIQUE
-   ,paid boolean DEFAULT FALSE NOT NULL
-   ,PRIMARY KEY (order_id, merchant_pub)
-   ,UNIQUE (h_contract_terms, merchant_pub)
-   );
-
--- Table with the proofs for each coin we deposited at the exchange
+  (order_serial BIGINT PRIMARY KEY
+  ,merchant_serial BIGINT NOT NULL
+    REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+  ,order_id VARCHAR NOT NULL
+  ,contract_terms BYTEA NOT NULL
+  ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+  ,creation_time INT8 NOT NULL
+  ,pay_deadline INT8 NOT NULL
+  ,refund_deadline INT8 NOT NULL
+  ,paid BOOLEAN DEFAULT FALSE NOT NULL
+  ,wired BOOLEAN DEFAULT FALSE NOT NULL
+  ,fulfillment_url VARCHAR NOT NULL
+  ,session_id VARCHAR DEFAULT '' NOT NULL
+  ,UNIQUE (merchant_serial, order_id)
+  ,UNIQUE (merchant_serial, h_contract_terms)
+  );
+COMMENT ON TABLE merchant_contract_terms
+  IS 'Contracts are orders that have been claimed by a wallet';
+COMMENT ON COLUMN merchant_contract_terms.order_id
+  IS 'Not a foreign key into merchant_orders because paid contracts persist 
after expiration';
+COMMENT ON COLUMN merchant_contract_terms.merchant_serial
+  IS 'Identifies the instance offering the contract';
+COMMENT ON COLUMN merchant_contract_terms.contract_terms
+  IS 'These contract terms include the wallet nonce';
+COMMENT ON COLUMN merchant_contract_terms.h_contract_terms
+  IS 'Hash over contract_terms';
+COMMENT ON COLUMN merchant_contract_terms.refund_deadline
+  IS 'By what times do refunds have to be approved (useful to reject refund 
requests)';
+COMMENT ON COLUMN merchant_contract_terms.paid
+  IS 'true implies the customer paid for this contract; order should be 
DELETEd from merchant_orders once paid is set to release merchant_order_locks; 
paid remains true even if the payment was later refunded';
+COMMENT ON COLUMN merchant_contract_terms.wired
+  IS 'true implies the exchange wired us the full amount for all non-refunded 
payments under this contract';
+COMMENT ON COLUMN merchant_contract_terms.fulfillment_url
+  IS 'also included in contract_terms, but we need it here to SELECT on it 
during repurchase detection';
+COMMENT ON COLUMN merchant_contract_terms.session_id
+  IS 'last session_id from we confirmed the paying client to use, empty string 
for none';
+COMMENT ON COLUMN merchant_contract_terms.pay_deadline
+  IS 'How long is the offer valid. After this time, the order can be garbage 
collected';
+CREATE INDEX IF NOT EXISTS merchant_contract_terms_by_merchant_and_expiration
+  ON merchant_contract_terms
+  (merchant_serial,pay_deadline);
+CREATE INDEX IF NOT EXISTS merchant_contract_terms_by_merchant_and_payment
+  ON merchant_contract_terms
+  (merchant_serial,paid);
+CREATE INDEX IF NOT EXISTS 
merchant_contract_terms_by_merchant_session_and_fulfillment
+  ON merchant_contract_terms
+  (merchant_serial,fulfillment_url,session_id);
+
+
+---------------- Payment and refunds ---------------------------
+
 CREATE TABLE IF NOT EXISTS merchant_deposits
-  (h_contract_terms BYTEA NOT NULL
-  ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
+  (deposit_serial BIGSERIAL PRIMARY KEY
+  ,order_serial BIGINT
+     REFERENCES merchant_contract_terms (order_serial) ON DELETE CASCADE
+  ,deposit_timestamp INT8 NOT NULL
   ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
   ,exchange_url VARCHAR NOT NULL
   ,amount_with_fee_val INT8 NOT NULL
@@ -56,132 +290,229 @@ CREATE TABLE IF NOT EXISTS merchant_deposits
   ,refund_fee_frac INT4 NOT NULL
   ,wire_fee_val INT8 NOT NULL
   ,wire_fee_frac INT4 NOT NULL
-  ,signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)
-  ,exchange_proof BYTEA NOT NULL
-  ,PRIMARY KEY (h_contract_terms, coin_pub)
-  ,FOREIGN KEY (h_contract_terms, merchant_pub)
-   REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub)
+  ,signkey_serial BIGINT NOT NULL
+     REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE 
CASCADE
+  ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
+  ,account_serial BIGINT NOT NULL
+     REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE
+  ,UNIQUE (order_serial, coin_pub)
   );
+COMMENT ON TABLE merchant_deposits
+  IS 'Table with the deposit confirmations for each coin we deposited at the 
exchange';
+COMMENT ON COLUMN merchant_deposits.signkey_serial
+  IS 'Online signing key of the exchange on the deposit confirmation';
+COMMENT ON COLUMN merchant_deposits.deposit_timestamp
+  IS 'Time when the exchange generated the deposit confirmation';
+COMMENT ON COLUMN merchant_deposits.exchange_sig
+  IS 'Signature of the exchange over the deposit confirmation';
+COMMENT ON COLUMN merchant_deposits.wire_fee_val
+  IS 'We MAY want to see if we should try to get this via 
merchant_exchange_wire_fees (not sure, may be too complicated with the date 
range, etc.)';
 
-CREATE TABLE IF NOT EXISTS merchant_proofs
-  (exchange_url VARCHAR NOT NULL
-  ,wtid BYTEA CHECK (LENGTH(wtid)=32)
-  ,execution_time INT8 NOT NULL
-  ,signkey_pub BYTEA NOT NULL CHECK (LENGTH(signkey_pub)=32)
-  ,proof BYTEA NOT NULL
-  ,PRIMARY KEY (wtid, exchange_url)
+CREATE TABLE IF NOT EXISTS merchant_refunds
+  (refund_serial BIGSERIAL PRIMARY KEY
+  ,order_serial BIGINT NOT NULL
+     REFERENCES merchant_contract_terms (order_serial) ON DELETE CASCADE
+  ,rtransaction_id BIGINT NOT NULL
+  ,refund_timestamp INT8 NOT NULL
+  ,coin_pub BYTEA NOT NULL
+  ,reason VARCHAR NOT NULL
+  ,refund_amount_val INT8 NOT NULL
+  ,refund_amount_frac INT4 NOT NULL
+  ,UNIQUE (order_serial, coin_pub, rtransaction_id)
   );
+COMMENT ON TABLE merchant_deposits
+  IS 'Refunds approved by the merchant (backoffice) logic, excludes abort 
refunds';
+COMMENT ON COLUMN merchant_refunds.rtransaction_id
+  IS 'Needed for uniqueness in case a refund is increased for the same order';
+COMMENT ON COLUMN merchant_refunds.refund_timestamp
+  IS 'Needed for grouping of refunds in the wallet UI; has no semantics in the 
protocol (only for UX), but should be from the time when the merchant 
internally approved the refund';
+CREATE INDEX IF NOT EXISTS merchant_refunds_by_coin_and_order
+  ON merchant_refunds
+  (coin_pub,order_serial);
+
+CREATE TABLE IF NOT EXISTS merchant_refund_proofs
+  (refund_serial BIGINT PRIMARY KEY
+     REFERENCES merchant_refunds (refund_serial) ON DELETE CASCADE
+  ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
+  ,refund_fee_val INT8 NOT NULL
+  ,refund_fee_frac INT4 NOT NULL
+  ,signkey_serial BIGINT NOT NULL
+     REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE 
CASCADE
+);
+COMMENT ON TABLE merchant_refund_proofs
+  IS 'Refunds confirmed by the exchange (not all approved refunds are grabbed 
by the wallet)';
+COMMENT ON COLUMN merchant_refund_proofs.refund_fee_val
+  IS 'Refund fee exchange charged';
+
+-------------------- Wire transfers ---------------------------
 
--- Note that h_contract_terms + coin_pub may actually be unknown to
--- us, e.g. someone else deposits something for us at the exchange.
--- Hence those cannot be foreign keys into deposits/transactions!
 CREATE TABLE IF NOT EXISTS merchant_transfers
-  (h_contract_terms BYTEA NOT NULL
-  ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
-  ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)
-  ,PRIMARY KEY (h_contract_terms, coin_pub)
-  );
-CREATE INDEX IF NOT EXISTS merchant_transfers_by_coin
-  ON merchant_transfers
-  (h_contract_terms
-  ,coin_pub);
-CREATE INDEX IF NOT EXISTS merchant_transfers_by_wtid
-  ON merchant_transfers
-  (wtid);
-
-CREATE TABLE IF NOT EXISTS exchange_wire_fees
-  (exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32)
-  ,h_wire_method BYTEA NOT NULL CHECK (LENGTH(h_wire_method)=64)
+  (credit_serial BIGSERIAL PRIMARY KEY
+  ,exchange_url VARCHAR NOT NULL
+  ,wtid BYTEA CHECK (LENGTH(wtid)=32)
+  ,credit_amount_val INT8 NOT NULL
+  ,credit_amount_frac INT4 NOT NULL
+  ,account_serial BIGINT NOT NULL
+     REFERENCES merchant_accounts (account_serial) ON DELETE CASCADE
+  ,verified BOOLEAN NOT NULL DEFAULT FALSE
+  ,confirmed BOOLEAN NOT NULL DEFAULT FALSE
+  ,UNIQUE (wtid, exchange_url)
+  );
+COMMENT ON TABLE merchant_transfers
+  IS 'table represents the information provided by the (trusted) merchant 
about incoming wire transfers';
+COMMENT ON COLUMN merchant_transfers.verified
+  IS 'true once we got an acceptable response from the exchange for this 
transfer';
+COMMENT ON COLUMN merchant_transfers.confirmed
+  IS 'true once the merchant confirmed that this transfer was received';
+COMMENT ON COLUMN merchant_transfers.credit_amount_val
+  IS 'actual value of the (aggregated) wire transfer, excluding the wire fee';
+
+CREATE TABLE IF NOT EXISTS merchant_transfer_signatures
+  (credit_serial BIGINT PRIMARY KEY
+     REFERENCES merchant_transfers (credit_serial) ON DELETE CASCADE
+  ,signkey_serial BIGINT NOT NULL
+     REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE 
CASCADE
   ,wire_fee_val INT8 NOT NULL
   ,wire_fee_frac INT4 NOT NULL
-  ,closing_fee_val INT8 NOT NULL
-  ,closing_fee_frac INT4 NOT NULL
-  ,start_date INT8 NOT NULL
-  ,end_date INT8 NOT NULL
+  ,execution_time INT8 NOT NULL
   ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
-  ,PRIMARY KEY (exchange_pub,h_wire_method,start_date,end_date)
   );
+COMMENT ON TABLE merchant_transfer_signatures
+  IS 'table represents the main information returned from the /transfer 
request to the exchange.';
+COMMENT ON COLUMN merchant_transfer_signatures.execution_time
+  IS 'Execution time as claimed by the exchange, roughly matches time seen by 
merchant';
 
-CREATE TABLE IF NOT EXISTS merchant_refunds
-  (rtransaction_id BIGSERIAL UNIQUE
-  ,merchant_pub BYTEA NOT NULL
-  ,h_contract_terms BYTEA NOT NULL
-  ,coin_pub BYTEA NOT NULL
-  ,reason VARCHAR NOT NULL
-  ,refund_amount_val INT8 NOT NULL
-  ,refund_amount_frac INT4 NOT NULL
-  ,FOREIGN KEY (h_contract_terms, coin_pub)
-   REFERENCES merchant_deposits (h_contract_terms, coin_pub)
-  ,FOREIGN KEY (h_contract_terms, merchant_pub)
-   REFERENCES merchant_contract_terms (h_contract_terms, merchant_pub)
-  ,PRIMARY KEY (h_contract_terms, merchant_pub, coin_pub, rtransaction_id)
+CREATE TABLE IF NOT EXISTS merchant_transfer_to_coin
+  (deposit_serial BIGINT UNIQUE NOT NULL
+     REFERENCES merchant_deposits (deposit_serial) ON DELETE CASCADE
+  ,credit_serial BIGINT NOT NULL
+     REFERENCES merchant_transfers (credit_serial) ON DELETE CASCADE
+  ,offset_in_exchange_list INT8 NOT NULL
+  ,exchange_deposit_value_val INT8 NOT NULL
+  ,exchange_deposit_value_frac INT4 NOT NULL
+  ,exchange_deposit_fee_val INT8 NOT NULL
+  ,exchange_deposit_fee_frac INT4 NOT NULL
   );
+CREATE INDEX IF NOT EXISTS merchant_transfers_by_credit
+  ON merchant_transfer_to_coin
+  (credit_serial);
+COMMENT ON TABLE merchant_transfer_to_coin
+  IS 'Mapping of (credit) transfers to (deposited) coins';
+COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_value_val
+  IS 'Deposit value as claimed by the exchange, should match our values in 
merchant_deposits minus refunds';
+COMMENT ON COLUMN merchant_transfer_to_coin.exchange_deposit_fee_val
+  IS 'Deposit value as claimed by the exchange, should match our values in 
merchant_deposits';
 
-CREATE TABLE IF NOT EXISTS merchant_refund_proofs
-  (rtransaction_id BIGSERIAL UNIQUE
-  ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
-  ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
-  ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
+CREATE TABLE IF NOT EXISTS merchant_deposit_to_transfer
+  (deposit_serial BIGINT NOT NULL
+     REFERENCES merchant_deposits (deposit_serial) ON DELETE CASCADE
+  ,coin_contribution_value_val INT8 NOT NULL
+  ,coin_contribution_value_frac INT4 NOT NULL
+  ,credit_serial BIGINT NOT NULL
+     REFERENCES merchant_transfers (credit_serial)
+  ,execution_time INT8 NOT NULL
+  ,signkey_serial BIGINT NOT NULL
+     REFERENCES merchant_exchange_signing_keys (signkey_serial) ON DELETE 
CASCADE
   ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
-  ,exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32)
-  ,FOREIGN KEY (h_contract_terms, merchant_pub, coin_pub, rtransaction_id)
-   REFERENCES merchant_refunds (h_contract_terms, merchant_pub, coin_pub, 
rtransaction_id)
-  ,PRIMARY KEY (h_contract_terms, merchant_pub, coin_pub, rtransaction_id)
+  ,UNIQUE(deposit_serial,credit_serial)
 );
+COMMENT ON TABLE merchant_deposit_to_transfer
+  IS 'Mapping of deposits to (possibly unconfirmed) wire transfers; NOTE: not 
used yet';
+COMMENT ON COLUMN merchant_deposit_to_transfer.execution_time
+  IS 'Execution time as claimed by the exchange, roughly matches time seen by 
merchant';
+
+
+-------------------------- Tipping ---------------------------
 
--- balances of the reserves available for tips
 CREATE TABLE IF NOT EXISTS merchant_tip_reserves
-  (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)
+  (reserve_serial BIGSERIAL PRIMARY KEY
+  ,reserve_pub BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_pub)=32)
+  ,merchant_serial BIGINT NOT NULL
+    REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+  ,creation_time INT8 NOT NULL
   ,expiration INT8 NOT NULL
-  ,balance_val INT8 NOT NULL
-  ,balance_frac INT4 NOT NULL
-  ,PRIMARY KEY (reserve_priv)
+  ,merchant_initial_balance_val INT8 NOT NULL
+  ,merchant_initial_balance_frac INT4 NOT NULL
+  ,exchange_initial_balance_val INT8 NOT NULL DEFAULT 0
+  ,exchange_initial_balance_frac INT4 NOT NULL DEFAULT 0
+  ,tips_committed_val INT8 NOT NULL DEFAULT 0
+  ,tips_committed_frac INT4 NOT NULL DEFAULT 0
+  ,tips_picked_up_val INT8 NOT NULL DEFAULT 0
+  ,tips_picked_up_frac INT4 NOT NULL DEFAULT 0
   );
+COMMENT ON TABLE merchant_tip_reserves
+  IS 'balances of the reserves available for tips';
+COMMENT ON COLUMN merchant_tip_reserves.expiration
+  IS 'FIXME: EXCHANGE API needs to tell us when reserves close if we are to 
compute this';
+COMMENT ON COLUMN merchant_tip_reserves.merchant_initial_balance_val
+  IS 'Set to the initial balance the merchant told us when creating the 
reserve';
+COMMENT ON COLUMN merchant_tip_reserves.exchange_initial_balance_val
+  IS 'Set to the initial balance the exchange told us when we queried the 
reserve status';
+COMMENT ON COLUMN merchant_tip_reserves.tips_committed_val
+  IS 'Amount of outstanding approved tips that have not been picked up';
+COMMENT ON COLUMN merchant_tip_reserves.tips_picked_up_val
+  IS 'Total amount tips that have been picked up from this reserve';
 
--- table where we remember when tipping reserves where established / enabled
-CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits
-  (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)
-  ,credit_uuid BYTEA UNIQUE NOT NULL CHECK (LENGTH(credit_uuid)=64)
-  ,timestamp INT8 NOT NULL
-  ,amount_val INT8 NOT NULL
-  ,amount_frac INT4 NOT NULL
-  ,PRIMARY KEY (credit_uuid)
+CREATE TABLE IF NOT EXISTS merchant_tip_reserve_keys
+  (reserve_serial BIGINT NOT NULL UNIQUE
+     REFERENCES merchant_tip_reserves (reserve_serial) ON DELETE CASCADE
+  ,reserve_priv BYTEA NOT NULL UNIQUE CHECK (LENGTH(reserve_priv)=32)
+  ,exchange_url VARCHAR NOT NULL
   );
+COMMENT ON TABLE merchant_tip_reserves
+  IS 'private keys of reserves that have not been deleted';
 
--- tips that have been authorized
 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_url VARCHAR NOT NULL
+  (tip_serial BIGSERIAL PRIMARY KEY
+  ,reserve_serial BIGINT NOT NULL
+     REFERENCES merchant_tip_reserves (reserve_serial) ON DELETE CASCADE
+  ,tip_id BYTEA NOT NULL UNIQUE CHECK (LENGTH(tip_id)=64)
   ,justification VARCHAR NOT NULL
-  ,extra BYTEA NOT NULL
-  ,timestamp INT8 NOT NULL
-  ,amount_val INT8 NOT NULL /* overall tip amount */
+  ,next_url VARCHAR NOT NULL
+  ,expiration INT8 NOT NULL
+  ,amount_val INT8 NOT NULL
   ,amount_frac INT4 NOT NULL
-  ,left_val INT8 NOT NULL /* tip amount not yet picked up */
-  ,left_frac INT4 NOT NULL
-  ,PRIMARY KEY (tip_id)
+  ,picked_up_val INT8 NOT NULL DEFAULT 0
+  ,picked_up_frac INT4 NOT NULL DEFAULT 0
+  ,was_picked_up BOOLEAN NOT NULL DEFAULT FALSE
   );
+CREATE INDEX IF NOT EXISTS merchant_tips_by_pickup_and_expiration
+  ON merchant_tips
+    (was_picked_up,expiration);
+COMMENT ON TABLE merchant_tips
+  IS 'tips that have been authorized';
+COMMENT ON COLUMN merchant_tips.amount_val
+  IS 'Overall tip amount';
+COMMENT ON COLUMN merchant_tips.picked_up_val
+  IS 'Tip amount left to be picked up';
+COMMENT ON COLUMN merchant_tips.reserve_serial
+  IS 'Reserve from which this tip is funded';
+COMMENT ON COLUMN merchant_tips.expiration
+  IS 'by when does the client have to pick up the tip';
 
--- tips that have been picked up
 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)
+  (pickup_serial BIGSERIAL PRIMARY KEY NOT NULL
+  ,tip_serial BIGINT NOT NULL
+      REFERENCES merchant_tips (tip_serial) ON DELETE CASCADE
+  ,pickup_id BYTEA NOT NULL UNIQUE CHECK (LENGTH(pickup_id)=64)
   ,amount_val INT8 NOT NULL
   ,amount_frac INT4 NOT NULL
-  ,PRIMARY KEY (pickup_id)
   );
+COMMENT ON TABLE merchant_tip_pickups
+  IS 'tips that have been picked up';
+COMMENT ON COLUMN merchant_tips.amount_val
+  IS 'total transaction cost for all coins including withdraw fees';
 
--- sessions and their order_id/fulfillment_url mapping
-CREATE TABLE IF NOT EXISTS merchant_session_info
-  (session_id VARCHAR NOT NULL
-  ,fulfillment_url VARCHAR NOT NULL
-  ,order_id VARCHAR NOT NULL
-  ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
-  ,timestamp INT8 NOT NULL
-  ,PRIMARY KEY (session_id, fulfillment_url, merchant_pub)
-  ,UNIQUE (session_id, fulfillment_url, order_id, merchant_pub)
+CREATE TABLE IF NOT EXISTS merchant_tip_pickup_signatures
+  (pickup_serial INT8 NOT NULL
+     REFERENCES merchant_tip_pickups (pickup_serial) ON DELETE CASCADE
+  ,coin_offset INT4 NOT NULL
+  ,blind_sig BYTEA NOT NULL
+  ,PRIMARY KEY (pickup_serial, coin_offset)
   );
+COMMENT ON TABLE merchant_tip_pickup_signatures
+  IS 'blind signatures we got from the exchange during the tip pickup';
+
 
 -- Complete transaction
 COMMIT;
diff --git a/src/backenddb/merchantdb_plugin.c 
b/src/backenddb/merchantdb_plugin.c
index 473c659..a8e046e 100644
--- a/src/backenddb/merchantdb_plugin.c
+++ b/src/backenddb/merchantdb_plugin.c
@@ -32,7 +32,7 @@
  * @return #GNUNET_OK on success
  */
 struct TALER_MERCHANTDB_Plugin *
-TALER_MERCHANTDB_plugin_load (struct GNUNET_CONFIGURATION_Handle *cfg)
+TALER_MERCHANTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
 {
   char *plugin_name;
   char *lib_name;
@@ -54,7 +54,7 @@ TALER_MERCHANTDB_plugin_load (struct 
GNUNET_CONFIGURATION_Handle *cfg)
                           plugin_name);
   GNUNET_free (plugin_name);
   plugin = GNUNET_PLUGIN_load (lib_name,
-                               cfg);
+                               (void *) cfg);
   if (NULL != plugin)
     plugin->library_name = lib_name;
   else
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 697732d..1360d90 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -26,6 +26,7 @@
 #include <taler/taler_util.h>
 #include <taler/taler_pq_lib.h>
 #include <taler/taler_json_lib.h>
+#include <taler/taler_mhd_lib.h>
 #include "taler_merchantdb_plugin.h"
 
 /**
@@ -41,7 +42,8 @@
  * @param field name of the database field to fetch amount from
  * @param amountp[out] pointer to amount to set
  */
-#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) TALER_PQ_result_spec_amount 
( \
+#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) \
+  TALER_PQ_result_spec_amount (                    \
     field,pg->currency,amountp)
 
 /**
@@ -51,8 +53,8 @@
  * @param field name of the database field to fetch amount from
  * @param amountp[out] pointer to amount to set
  */
-#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \
-                                        amountp) 
TALER_PQ_result_spec_amount_nbo ( \
+#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, amountp) \
+  TALER_PQ_result_spec_amount_nbo (                     \
     field,pg->currency,amountp)
 
 
@@ -63,7 +65,8 @@
  * @param field name of the database field to fetch amount from
  * @param amountp[out] pointer to amount to set
  */
-#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) TALER_PQ_result_spec_amount 
( \
+#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) \
+  TALER_PQ_result_spec_amount (                    \
     field,pg->currency,amountp)
 
 
@@ -102,6 +105,9 @@ struct PostgresClosure
 };
 
 
+/* ********************* NEW API ************************** */
+
+
 /**
  * Drop merchant tables
  *
@@ -125,18 +131,6 @@ postgres_drop_tables (void *cls)
 }
 
 
-/**
- * Check that the database connection is still up.
- *
- * @param pg connection to check
- */
-static void
-check_connection (struct PostgresClosure *pg)
-{
-  GNUNET_PQ_reconnect_if_down (pg->conn);
-}
-
-
 /**
  * Do a pre-flight check that we are not in an uncommitted transaction.
  * If we are, try to commit the previous transaction and output a warning.
@@ -173,6 +167,18 @@ postgres_preflight (void *cls)
 }
 
 
+/**
+ * Check that the database connection is still up.
+ *
+ * @param pg connection to check
+ */
+static void
+check_connection (struct PostgresClosure *pg)
+{
+  GNUNET_PQ_reconnect_if_down (pg->conn);
+}
+
+
 /**
  * Start a transaction.
  *
@@ -208,6 +214,41 @@ postgres_start (void *cls,
 }
 
 
+/**
+ * Start a transaction in 'read committed' mode.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging),
+ *             must point to a constant
+ * @return #GNUNET_OK on success
+ */
+static int
+postgres_start_read_committed (void *cls,
+                               const char *name)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ 
COMMITTED"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+
+  check_connection (pg);
+  postgres_preflight (pg);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Starting merchant DB transaction (READ COMMITTED)\n");
+  if (GNUNET_OK !=
+      GNUNET_PQ_exec_statements (pg->conn,
+                                 es))
+  {
+    TALER_LOG_ERROR ("Failed to start transaction\n");
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  pg->transaction_name = name;
+  return GNUNET_OK;
+}
+
+
 /**
  * Roll back the current transaction of a database connection.
  *
@@ -256,560 +297,916 @@ postgres_commit (void *cls)
 
 
 /**
- * Retrieve proposal data given its proposal data's hashcode
+ * Context for lookup_instances().
+ */
+struct LookupInstancesContext
+{
+  /**
+   * Function to call with the results.
+   */
+  TALER_MERCHANTDB_InstanceCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Database context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Instance settings, valid only during find_instances_cb().
+   */
+  struct TALER_MERCHANTDB_InstanceSettings is;
+
+  /**
+   * Instance serial number, valid only during find_instances_cb().
+   */
+  uint64_t instance_serial;
+
+  /**
+   * Public key of the current instance, valid only during find_instances_cb().
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * Set to the return value on errors.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+
+  /**
+   * true if we only are interested in instances for which we have the private 
key.
+   */
+  bool active_only;
+};
+
+
+/**
+ * We are processing an instances lookup and have the @a accounts.
+ * Find the private key if possible, and invoke the callback.
  *
- * @param cls closure
- * @param contract_terms where to store the retrieved proposal data
- * @param h_contract_terms proposal data's hashcode that will be used to
- * perform the lookup
- * @return transaction status
+ * @param lic context we are handling
+ * @param num_accounts length of @a accounts array
+ * @param accounts information about accounts of the instance in @a lic
  */
-static enum GNUNET_DB_QueryStatus
-postgres_find_contract_terms_from_hash (void *cls,
-                                        json_t **contract_terms,
-                                        const struct
-                                        GNUNET_HashCode *h_contract_terms,
-                                        const struct
-                                        TALER_MerchantPublicKeyP *merchant_pub)
+static void
+call_with_accounts (struct LookupInstancesContext *lic,
+                    unsigned int num_accounts,
+                    const struct TALER_MERCHANTDB_AccountDetails accounts[])
 {
-  struct PostgresClosure *pg = cls;
+  struct PostgresClosure *pg = lic->pg;
+  enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+    GNUNET_PQ_query_param_uint64 (&lic->instance_serial),
     GNUNET_PQ_query_param_end
   };
+  struct TALER_MerchantPrivateKeyP merchant_priv;
   struct GNUNET_PQ_ResultSpec rs[] = {
-    TALER_PQ_result_spec_json ("contract_terms",
-                               contract_terms),
+    GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
+                                          &merchant_priv),
     GNUNET_PQ_result_spec_end
   };
 
-  check_connection (pg);
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   
"find_contract_terms_from_hash",
-                                                   params,
-                                                   rs);
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "lookup_instance_private_key",
+                                                 params,
+                                                 rs);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+    return;
+  }
+  if ( (0 == qs) &&
+       (lic->active_only) )
+    return; /* skip, not interesting */
+  lic->cb (lic->cb_cls,
+           &lic->merchant_pub,
+           (0 == qs) ? NULL : &merchant_priv,
+           &lic->is,
+           num_accounts,
+           accounts);
 }
 
 
 /**
- * Retrieve proposal data given its proposal data's hashcode
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
  *
- * @param cls closure
- * @param contract_terms where to store the retrieved proposal data
- * @param h_contract_terms proposal data's hashcode that will be used to
- * perform the lookup
- * @return transaction status
+ * @param cls of type `struct FindInstancesContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
  */
-static enum GNUNET_DB_QueryStatus
-postgres_find_paid_contract_terms_from_hash (void *cls,
-                                             json_t **contract_terms,
-                                             const struct
-                                             GNUNET_HashCode *h_contract_terms,
-                                             const struct
-                                             TALER_MerchantPublicKeyP *
-                                             merchant_pub)
+static void
+lookup_accounts_cb (void *cls,
+                    PGresult *result,
+                    unsigned int num_results)
 {
-  struct PostgresClosure *pg = cls;
-  struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
-    GNUNET_PQ_query_param_end
-  };
-  struct GNUNET_PQ_ResultSpec rs[] = {
-    TALER_PQ_result_spec_json ("contract_terms",
-                               contract_terms),
-    GNUNET_PQ_result_spec_end
-  };
+  struct LookupInstancesContext *lic = cls;
+  char *paytos[num_results];
+  struct TALER_MERCHANTDB_AccountDetails accounts[num_results];
 
-  /* no preflight check here, runs in its own transaction from
-     caller (in /pay case) */
-  check_connection (pg);
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   
"find_paid_contract_terms_from_hash",
-                                                   params,
-                                                   rs);
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    uint8_t active;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+                                            &accounts[i].h_wire),
+      GNUNET_PQ_result_spec_auto_from_type ("salt",
+                                            &accounts[i].salt),
+      GNUNET_PQ_result_spec_string ("payto_uri",
+                                    &paytos[i]),
+      GNUNET_PQ_result_spec_auto_from_type ("active",
+                                            &active),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      for (unsigned int j = 0; j < i; j++)
+        GNUNET_free (paytos[j]);
+      return;
+    }
+    accounts[i].active = (0 != active);
+    accounts[i].payto_uri = paytos[i];
+  }
+  call_with_accounts (lic,
+                      num_results,
+                      accounts);
+  for (unsigned int i = 0; i < num_results; i++)
+    GNUNET_free (paytos[i]);
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about instances.
+ *
+ * @param cls of type `struct FindInstancesContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_instances_cb (void *cls,
+                     PGresult *result,
+                     unsigned int num_results)
+{
+  struct LookupInstancesContext *lic = cls;
+  struct PostgresClosure *pg = lic->pg;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("merchant_serial",
+                                    &lic->instance_serial),
+      GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+                                            &lic->merchant_pub),
+      GNUNET_PQ_result_spec_string ("merchant_id",
+                                    &lic->is.id),
+      GNUNET_PQ_result_spec_string ("merchant_name",
+                                    &lic->is.name),
+      TALER_PQ_result_spec_json ("address",
+                                 &lic->is.address),
+      TALER_PQ_result_spec_json ("jurisdiction",
+                                 &lic->is.jurisdiction),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("default_max_deposit_fee",
+                                   &lic->is.default_max_deposit_fee),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("default_max_wire_fee",
+                                   &lic->is.default_max_wire_fee),
+      GNUNET_PQ_result_spec_uint32 ("default_wire_fee_amortization",
+                                    &lic->is.default_wire_fee_amortization),
+      GNUNET_PQ_result_spec_relative_time ("default_wire_transfer_delay",
+                                           
&lic->is.default_wire_transfer_delay),
+      GNUNET_PQ_result_spec_relative_time ("default_pay_delay",
+                                           &lic->is.default_pay_delay),
+      GNUNET_PQ_result_spec_end
+    };
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_uint64 (&lic->instance_serial),
+      GNUNET_PQ_query_param_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    lic->qs = GNUNET_PQ_eval_prepared_multi_select (lic->pg->conn,
+                                                    "lookup_accounts",
+                                                    params,
+                                                    &lookup_accounts_cb,
+                                                    lic);
+    if (0 > lic->qs)
+    {
+      /* lookup_accounts_cb() did not run, still notify about the
+         account-less instance! */
+      call_with_accounts (lic,
+                          0,
+                          NULL);
+    }
+    GNUNET_PQ_cleanup_result (rs);
+    if (0 > lic->qs)
+      break;
+  }
 }
 
 
 /**
- * Retrieve proposal data given its order id.  Ignores if the
- * proposal has been paid or not.
+ * Lookup all of the instances this backend has configured.
  *
  * @param cls closure
- * @param[out] contract_terms where to store the retrieved contract terms
- * @param order id order id used to perform the lookup
- * @return transaction status
+ * @param active_only only find 'active' instances
+ * @param cb function to call on all instances found
+ * @param cb_cls closure for @a cb
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_contract_terms (void *cls,
-                              json_t **contract_terms,
-                              const char *order_id,
-                              const struct
-                              TALER_MerchantPublicKeyP *merchant_pub)
+postgres_lookup_instances (void *cls,
+                           bool active_only,
+                           TALER_MERCHANTDB_InstanceCallback cb,
+                           void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
+  struct LookupInstancesContext lic = {
+    .cb = cb,
+    .cb_cls = cb_cls,
+    .active_only = active_only,
+    .pg = pg
+  };
   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_end
   };
-  struct GNUNET_PQ_ResultSpec rs[] = {
-    TALER_PQ_result_spec_json ("contract_terms",
-                               contract_terms),
-    GNUNET_PQ_result_spec_end
-  };
+  enum GNUNET_DB_QueryStatus qs;
 
-  *contract_terms = NULL;
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Finding contract term, order_id: '%s', merchant_pub: '%s'.\n",
-              order_id,
-              TALER_B2S (merchant_pub));
   check_connection (pg);
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "find_contract_terms",
-                                                   params,
-                                                   rs);
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_instances",
+                                             params,
+                                             &lookup_instances_cb,
+                                             &lic);
+  if (0 > lic.qs)
+    return lic.qs;
+  return qs;
 }
 
 
 /**
- * Retrieve order given its order id and the instance's merchant public key.
+ * Insert information about an instance into our database.
  *
  * @param cls closure
- * @param[out] contract_terms where to store the retrieved contract terms
- * @param order id order id used to perform the lookup
- * @param merchant_pub merchant public key that identifies the instance
- * @return transaction status
+ * @param merchant_pub public key of the instance
+ * @param merchant_priv private key of the instance
+ * @param is details about the instance
+ * @return database result code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_order (void *cls,
-                     json_t **contract_terms,
-                     const char *order_id,
-                     const struct TALER_MerchantPublicKeyP *merchant_pub)
+postgres_insert_instance (void *cls,
+                          const struct TALER_MerchantPublicKeyP *merchant_pub,
+                          const struct TALER_MerchantPrivateKeyP 
*merchant_priv,
+                          const struct TALER_MERCHANTDB_InstanceSettings *is)
 {
   struct PostgresClosure *pg = cls;
   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_string (is->id),
+    GNUNET_PQ_query_param_string (is->name),
+    TALER_PQ_query_param_json (is->address),
+    TALER_PQ_query_param_json (is->jurisdiction),
+    TALER_PQ_query_param_amount (&is->default_max_deposit_fee),
+    TALER_PQ_query_param_amount (&is->default_max_wire_fee),
+    GNUNET_PQ_query_param_uint32 (&is->default_wire_fee_amortization),
+    GNUNET_PQ_query_param_relative_time (
+      &is->default_wire_transfer_delay),
+    GNUNET_PQ_query_param_relative_time (&is->default_pay_delay),
     GNUNET_PQ_query_param_end
   };
-  struct GNUNET_PQ_ResultSpec rs[] = {
-    TALER_PQ_result_spec_json ("contract_terms",
-                               contract_terms),
-    GNUNET_PQ_result_spec_end
+  struct GNUNET_PQ_QueryParam params_priv[] = {
+    GNUNET_PQ_query_param_auto_from_type (merchant_priv),
+    GNUNET_PQ_query_param_string (is->id),
+    GNUNET_PQ_query_param_end
   };
+  enum GNUNET_DB_QueryStatus qs;
 
-  *contract_terms = NULL;
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Finding contract term, order_id: '%s', merchant_pub: '%s'.\n",
-              order_id,
-              TALER_B2S (merchant_pub));
   check_connection (pg);
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "find_order",
-                                                   params,
-                                                   rs);
+  qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                           "insert_instance",
+                                           params);
+  if (qs <= 0)
+    return qs;
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_keys",
+                                             params_priv);
 }
 
 
 /**
- * Insert proposal data and its hashcode into db
+ * Insert information about an instance's account into our database.
  *
  * @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
+ * @param id identifier of the instance
+ * @param account_details details about the account
+ * @return database result code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_insert_contract_terms (void *cls,
-                                const char *order_id,
-                                const struct
-                                TALER_MerchantPublicKeyP *merchant_pub,
-                                struct GNUNET_TIME_Absolute timestamp,
-                                const json_t *contract_terms)
+postgres_insert_account (
+  void *cls,
+  const char *id,
+  const struct
+  TALER_MERCHANTDB_AccountDetails *account_details)
 {
   struct PostgresClosure *pg = cls;
-  struct GNUNET_HashCode h_contract_terms;
+  uint8_t active = account_details->active;
   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),
-    TALER_PQ_query_param_json (contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
+    GNUNET_PQ_query_param_string (id),
+    GNUNET_PQ_query_param_auto_from_type (&account_details->h_wire),
+    GNUNET_PQ_query_param_auto_from_type (&account_details->salt),
+    GNUNET_PQ_query_param_string (account_details->payto_uri),
+    GNUNET_PQ_query_param_auto_from_type (&active),
     GNUNET_PQ_query_param_end
   };
 
-  if (GNUNET_OK !=
-      TALER_JSON_hash (contract_terms,
-                       &h_contract_terms))
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "inserting contract terms: order_id: %s, merchant_pub: %s, 
h_contract_terms: %s.\n",
-              order_id,
-              TALER_B2S (merchant_pub),
-              GNUNET_h2s (&h_contract_terms));
   check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_contract_terms",
+                                             "insert_account",
                                              params);
 }
 
 
 /**
- * Insert order into the DB.
+ * Delete private key of an instance from our database.
  *
  * @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
+ * @param merchant_id identifier of the instance
+ * @return database result code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_insert_order (void *cls,
-                       const char *order_id,
-                       const struct TALER_MerchantPublicKeyP *merchant_pub,
-                       struct GNUNET_TIME_Absolute timestamp,
-                       const json_t *contract_terms)
+postgres_delete_instance_private_key (
+  void *cls,
+  const char *merchant_id)
 {
   struct PostgresClosure *pg = cls;
   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),
-    TALER_PQ_query_param_json (contract_terms),
+    GNUNET_PQ_query_param_string (merchant_id),
     GNUNET_PQ_query_param_end
   };
 
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "inserting order: order_id: %s, merchant_pub: %s.\n",
-              order_id,
-              TALER_B2S (merchant_pub));
   check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_order",
+                                             "delete_key",
                                              params);
 }
 
 
 /**
- * Mark contract terms as paid.  Needed by /history as only paid
- * contracts must be shown.
- *
- * NOTE: we can't get the list of (paid) 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).
+ * Purge an instance and all associated information from our database.
+ * Highly likely to cause undesired data loss. Use with caution.
  *
  * @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
+ * @param merchant_id identifier of the instance
+ * @return database result code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_mark_proposal_paid (void *cls,
-                             const struct GNUNET_HashCode *h_contract_terms,
-                             const struct
-                             TALER_MerchantPublicKeyP *merchant_pub)
+postgres_purge_instance (void *cls,
+                         const char *merchant_id)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+    GNUNET_PQ_query_param_string (merchant_id),
     GNUNET_PQ_query_param_end
   };
 
-  TALER_LOG_DEBUG ("Marking proposal paid, h_contract_terms: '%s',"
-                   " merchant_pub: '%s'\n",
-                   GNUNET_h2s (h_contract_terms),
-                   TALER_B2S (merchant_pub));
+  check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "mark_proposal_paid",
+                                             "purge_instance",
                                              params);
 }
 
 
 /**
- * Store the order ID that was used to pay for a resource within a session.
+ * Update information about an instance into our database.
  *
  * @param cls closure
- * @param session_id session id
- * @param fulfillment_url URL that canonically identifies the resource
- *        being paid for
- * @param order_id the order ID that was used when paying for the resource URL
- * @param merchant_pub public key of the merchant, identifying the instance
- * @return transaction status
+ * @param is details about the instance
+ * @return database result code
  */
-enum GNUNET_DB_QueryStatus
-postgres_insert_session_info (void *cls,
-                              const char *session_id,
-                              const char *fulfillment_url,
-                              const char *order_id,
-                              const struct
-                              TALER_MerchantPublicKeyP *merchant_pub)
+static enum GNUNET_DB_QueryStatus
+postgres_update_instance (void *cls,
+                          const struct TALER_MERCHANTDB_InstanceSettings *is)
 {
   struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (is->id),
+    GNUNET_PQ_query_param_string (is->name),
+    TALER_PQ_query_param_json (is->address),
+    TALER_PQ_query_param_json (is->jurisdiction),
+    TALER_PQ_query_param_amount (&is->default_max_deposit_fee),
+    TALER_PQ_query_param_amount (&is->default_max_wire_fee),
+    GNUNET_PQ_query_param_uint32 (&is->default_wire_fee_amortization),
+    GNUNET_PQ_query_param_relative_time (
+      &is->default_wire_transfer_delay),
+    GNUNET_PQ_query_param_relative_time (&is->default_pay_delay),
+    GNUNET_PQ_query_param_end
+  };
 
-  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "update_instance",
+                                             params);
+}
+
+
+/**
+ * Set an instance's account in our database to "inactive".
+ *
+ * @param cls closure
+ * @param h_wire hash of the wire account to set to inactive
+ * @return database result code
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_inactivate_account (void *cls,
+                             const struct GNUNET_HashCode *h_wire)
+{
+  struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_string (session_id),
-    GNUNET_PQ_query_param_string (fulfillment_url),
-    GNUNET_PQ_query_param_string (order_id),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
-    GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_auto_from_type (h_wire),
     GNUNET_PQ_query_param_end
   };
 
+  check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_session_info",
+                                             "inactivate_account",
                                              params);
 }
 
 
 /**
- * Retrieve the order ID that was used to pay for a resource within a session.
+ * Context used for postgres_lookup_products().
+ */
+struct LookupProductsContext
+{
+  /**
+   * Function to call with the results.
+   */
+  TALER_MERCHANTDB_ProductsCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Internal result.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about products.
+ *
+ * @param[in,out] cls of type `struct LookupProductsContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_products_cb (void *cls,
+                    PGresult *result,
+                    unsigned int num_results)
+{
+  struct LookupProductsContext *plc = cls;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    char *product_id;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("product_id",
+                                    &product_id),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      plc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    plc->cb (plc->cb_cls,
+             product_id);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Lookup all of the products the given instance has configured.
  *
  * @param cls closure
- * @param[out] order_id location to store the order ID that was used when
- *             paying for the resource URL
- * @param session_id session id
- * @param fulfillment_url URL that canonically identifies the resource
- *        being paid for
- * @param merchant_pub public key of the merchant, identifying the instance
- * @return transaction status
+ * @param instance_id instance to lookup products for
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
  */
-enum GNUNET_DB_QueryStatus
-postgres_find_session_info (void *cls,
-                            char **order_id,
-                            const char *session_id,
-                            const char *fulfillment_url,
-                            const struct TALER_MerchantPublicKeyP 
*merchant_pub)
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_products (void *cls,
+                          const char *instance_id,
+                          TALER_MERCHANTDB_ProductsCallback cb,
+                          void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
+  struct LookupProductsContext plc = {
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
 
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_products",
+                                             params,
+                                             &lookup_products_cb,
+                                             &plc);
+  if (0 != plc.qs)
+    return plc.qs;
+  return qs;
+}
+
+
+/**
+ * Lookup details about a particular product.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_id product to lookup
+ * @param[out] pd set to the product details on success, can be NULL
+ *             (in that case we only want to check if the product exists)
+ * @return database result code
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_product (void *cls,
+                         const char *instance_id,
+                         const char *product_id,
+                         struct TALER_MERCHANTDB_ProductDetails *pd)
+{
+  struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_string (fulfillment_url),
-    GNUNET_PQ_query_param_string (session_id),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (product_id),
     GNUNET_PQ_query_param_end
   };
   struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_string ("order_id",
-                                  order_id),
+    GNUNET_PQ_result_spec_string ("description",
+                                  &pd->description),
+    TALER_PQ_result_spec_json ("description_i18n",
+                               &pd->description_i18n),
+    GNUNET_PQ_result_spec_string ("unit",
+                                  &pd->unit),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("price",
+                                 &pd->price),
+    TALER_PQ_result_spec_json ("taxes",
+                               &pd->taxes),
+    GNUNET_PQ_result_spec_uint64 ("total_stock",
+                                  &pd->total_stock),
+    GNUNET_PQ_result_spec_uint64 ("total_sold",
+                                  &pd->total_sold),
+    GNUNET_PQ_result_spec_uint64 ("total_lost",
+                                  &pd->total_lost),
+    TALER_PQ_result_spec_json ("image",
+                               &pd->image),
+    TALER_PQ_result_spec_json ("address",
+                               &pd->address),
+    GNUNET_PQ_result_spec_absolute_time ("next_restock",
+                                         &pd->next_restock),
+    GNUNET_PQ_result_spec_end
+  };
+  struct GNUNET_PQ_ResultSpec rs_null[] = {
     GNUNET_PQ_result_spec_end
   };
-  // We don't clean up the result spec since we want
-  // to keep around the memory for order_id.
+
+  check_connection (pg);
   return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "find_session_info",
+                                                   "lookup_product",
                                                    params,
-                                                   rs);
+                                                   (NULL == pd)
+                                                   ? rs_null
+                                                   : rs);
 }
 
 
 /**
- * Insert payment confirmation from the exchange into the database.
+ * Delete information about a product.  Note that the transaction must
+ * enforce that no stocks are currently locked.
  *
  * @param cls closure
- * @param order_id identificator of the proposal associated with this revenue
- * @param merchant_pub merchant's public key
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param refund_fee fee the exchange will charge for refunding this coin
- * @param wire_fee wire fee changed by the exchange
- * @param signkey_pub public key used by the exchange for @a exchange_proof
- * @param exchange_proof proof from exchange that coin was accepted
- * @return transaction status
+ * @param instance_id instance to delete product of
+ * @param product_id product to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ *           if locks prevent deletion OR product unknown
  */
 static enum GNUNET_DB_QueryStatus
-postgres_store_deposit (void *cls,
-                        const struct GNUNET_HashCode *h_contract_terms,
-                        const struct TALER_MerchantPublicKeyP *merchant_pub,
-                        const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                        const char *exchange_url,
-                        const struct TALER_Amount *amount_with_fee,
-                        const struct TALER_Amount *deposit_fee,
-                        const struct TALER_Amount *refund_fee,
-                        const struct TALER_Amount *wire_fee,
-                        const struct TALER_ExchangePublicKeyP *signkey_pub,
-                        const json_t *exchange_proof)
+postgres_delete_product (void *cls,
+                         const char *instance_id,
+                         const char *product_id)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
-    GNUNET_PQ_query_param_string (exchange_url),
-    TALER_PQ_query_param_amount (amount_with_fee),
-    TALER_PQ_query_param_amount (deposit_fee),
-    TALER_PQ_query_param_amount (refund_fee),
-    TALER_PQ_query_param_amount (wire_fee),
-    GNUNET_PQ_query_param_auto_from_type (signkey_pub),
-    TALER_PQ_query_param_json (exchange_proof),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (product_id),
     GNUNET_PQ_query_param_end
   };
 
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Storing payment for h_contract_terms `%s', coin_pub: `%s', 
amount_with_fee: %s\n",
-              GNUNET_h2s (h_contract_terms),
-              TALER_B2S (coin_pub),
-              TALER_amount2s (amount_with_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Merchant pub is `%s'\n",
-              TALER_B2S (merchant_pub));
   check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_deposit",
+                                             "delete_product",
                                              params);
 }
 
 
 /**
- * Insert mapping of @a coin_pub and @a h_contract_terms to
- * corresponding @a wtid.
+ * Insert details about a particular product.
  *
  * @param cls closure
- * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub
- * @param coin_pub public key of the coin
- * @param wtid identifier of the wire transfer in which the exchange
- *             send us the money for the coin deposit
- * @return transaction status
+ * @param instance_id instance to insert product for
+ * @param product_id product identifier of product to insert
+ * @param pd the product details to insert
+ * @return database result code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_store_coin_to_transfer (void *cls,
-                                 const struct GNUNET_HashCode 
*h_contract_terms,
-                                 const struct
-                                 TALER_CoinSpendPublicKeyP *coin_pub,
-                                 const struct
-                                 TALER_WireTransferIdentifierRawP *wtid)
+postgres_insert_product (void *cls,
+                         const char *instance_id,
+                         const char *product_id,
+                         const struct TALER_MERCHANTDB_ProductDetails *pd)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
-    GNUNET_PQ_query_param_auto_from_type (wtid),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (product_id),
+    GNUNET_PQ_query_param_string (pd->description),
+    TALER_PQ_query_param_json (pd->description_i18n),
+    GNUNET_PQ_query_param_string (pd->unit),
+    TALER_PQ_query_param_json (pd->image),
+    TALER_PQ_query_param_json (pd->taxes),
+    TALER_PQ_query_param_amount (&pd->price),
+    GNUNET_PQ_query_param_uint64 (&pd->total_stock),
+    TALER_PQ_query_param_json (pd->address),
+    GNUNET_PQ_query_param_absolute_time (&pd->next_restock),
     GNUNET_PQ_query_param_end
   };
 
   check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_transfer",
+                                             "insert_product",
                                              params);
 }
 
 
 /**
- * Insert wire transfer confirmation from the exchange into the database.
+ * Update details about a particular product. Note that the
+ * transaction must enforce that the sold/stocked/lost counters
+ * are not reduced (i.e. by expanding the WHERE clause on the existing
+ * values).
  *
  * @param cls closure
- * @param exchange_url URL of the exchange
- * @param wtid identifier of the wire transfer
- * @param execution_time when was @a wtid executed
- * @param signkey_pub public key used by the exchange for @a exchange_proof
- * @param exchange_proof proof from exchange about what the deposit was for
- * @return transaction status
+ * @param instance_id instance to lookup products for
+ * @param product_id product to lookup
+ * @param[out] pd set to the product details on success, can be NULL
+ *             (in that case we only want to check if the product exists)
+ *             total_sold in @a pd is ignored, total_lost must not
+ *             exceed total_stock minus the existing total_sold;
+ *             total_sold and total_stock must be larger or equal to
+ *             the existing value;
+ * @return database result code, #GNUNET_DB_SUCCESS_NO_RESULTS if the
+ *         non-decreasing constraints are not met *or* if the product
+ *         does not yet exist.
  */
 static enum GNUNET_DB_QueryStatus
-postgres_store_transfer_to_proof (void *cls,
-                                  const char *exchange_url,
-                                  const struct
-                                  TALER_WireTransferIdentifierRawP *wtid,
-                                  struct GNUNET_TIME_Absolute execution_time,
-                                  const struct
-                                  TALER_ExchangePublicKeyP *signkey_pub,
-                                  const json_t *exchange_proof)
+postgres_update_product (void *cls,
+                         const char *instance_id,
+                         const char *product_id,
+                         const struct TALER_MERCHANTDB_ProductDetails *pd)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_string (exchange_url),
-    GNUNET_PQ_query_param_auto_from_type (wtid),
-    GNUNET_PQ_query_param_absolute_time (&execution_time),
-    GNUNET_PQ_query_param_auto_from_type (signkey_pub),
-    TALER_PQ_query_param_json (exchange_proof),
+    GNUNET_PQ_query_param_string (instance_id), /* $1 */
+    GNUNET_PQ_query_param_string (product_id),
+    GNUNET_PQ_query_param_string (pd->description),
+    TALER_PQ_query_param_json (pd->description_i18n),
+    GNUNET_PQ_query_param_string (pd->unit),
+    TALER_PQ_query_param_json (pd->image), /* $6 */
+    TALER_PQ_query_param_json (pd->taxes),
+    TALER_PQ_query_param_amount (&pd->price),
+    GNUNET_PQ_query_param_uint64 (&pd->total_stock),
+    GNUNET_PQ_query_param_uint64 (&pd->total_sold),
+    GNUNET_PQ_query_param_uint64 (&pd->total_lost), /* $11 */
+    TALER_PQ_query_param_json (pd->address),
+    GNUNET_PQ_query_param_absolute_time (&pd->next_restock),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "update_product",
+                                             params);
+}
+
+
+/**
+ * Lock stocks of a particular product. Note that the transaction must
+ * enforce that the "stocked-sold-lost >= locked" constraint holds.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_id product to lookup
+ * @param uuid the UUID that holds the lock
+ * @param quantity how many units should be locked
+ * @param expiration_time when should the lock expire
+ * @return database result code, #GNUNET_DB_SUCCESS_NO_RESULTS if the
+ *         product is unknown OR if there insufficient stocks remaining
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lock_product (void *cls,
+                       const char *instance_id,
+                       const char *product_id,
+                       const struct GNUNET_Uuid *uuid,
+                       uint64_t quantity,
+                       struct GNUNET_TIME_Absolute expiration_time)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (product_id),
+    GNUNET_PQ_query_param_auto_from_type (uuid),
+    GNUNET_PQ_query_param_uint64 (&quantity),
+    GNUNET_PQ_query_param_absolute_time (&expiration_time),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "lock_product",
+                                             params);
+}
+
+
+/**
+ * Delete information about an order.  Note that the transaction must
+ * enforce that the order is not awaiting payment anymore.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete order of
+ * @param order_id order to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ *           if pending payment prevents deletion OR order unknown
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_delete_order (void *cls,
+                       const char *instance_id,
+                       const char *order_id)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
+    GNUNET_PQ_query_param_absolute_time (&now),
     GNUNET_PQ_query_param_end
   };
 
   check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_proof",
+                                             "delete_order",
                                              params);
 }
 
 
 /**
- * Lookup for a proposal, respecting the signature used by the
- * /history's db methods.
+ * Retrieve order given its @a order_id and the @a instance_id.
  *
- * @param cls db plugin handle
- * @param order_id order id used to search for the proposal data
- * @param merchant_pub public key of the merchant using this method
- * @param cb the callback
- * @param cb_cls closure to pass to the callback
+ * @param cls closure
+ * @param instance_id instance to obtain order of
+ * @param order id order id used to perform the lookup
+ * @param[out] contract_terms where to store the retrieved contract terms,
+ *             NULL to only test if the order exists
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_contract_terms_history (void *cls,
-                                      const char *order_id,
-                                      const struct
-                                      TALER_MerchantPublicKeyP *merchant_pub,
-                                      TALER_MERCHANTDB_ProposalDataCallback cb,
-                                      void *cb_cls)
+postgres_lookup_order (void *cls,
+                       const char *instance_id,
+                       const char *order_id,
+                       json_t **contract_terms)
 {
   struct PostgresClosure *pg = cls;
-  json_t *contract_terms;
+  json_t *j;
   enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
     GNUNET_PQ_query_param_string (order_id),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
     GNUNET_PQ_query_param_end
   };
   struct GNUNET_PQ_ResultSpec rs[] = {
     TALER_PQ_result_spec_json ("contract_terms",
-                               &contract_terms),
+                               &j),
     GNUNET_PQ_result_spec_end
   };
 
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Finding contract term, order_id: '%s', instance_id: '%s'.\n",
+              order_id,
+              instance_id);
+  check_connection (pg);
   qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "find_contract_terms_history",
+                                                 "lookup_order",
                                                  params,
                                                  rs);
-  if (qs <= 0)
-    return qs;
-  if (NULL != cb)
-    cb (cb_cls,
-        order_id,
-        0,
-        contract_terms);
-  GNUNET_PQ_cleanup_result (rs);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  {
+    if (NULL != contract_terms)
+      *contract_terms = j;
+    else
+      json_decref (j);
+  }
+  else
+  {
+    /* just to be safe: NULL it */
+    if (NULL != contract_terms)
+      *contract_terms = NULL;
+  }
   return qs;
 }
 
 
 /**
- * Closure for #find_contracts_cb().
+ * Retrieve order summary given its @a order_id and the @a instance_id.
+ *
+ * @param cls closure
+ * @param instance_id instance to obtain order of
+ * @param order_id order id used to perform the lookup
+ * @param[out] timestamp when was the order created
+ * @param[out] order_serial under which serial do we keep this order
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_order_summary (void *cls,
+                               const char *instance_id,
+                               const char *order_id,
+                               struct GNUNET_TIME_Absolute *timestamp,
+                               uint64_t *order_serial)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_uint64 ("order_serial",
+                                  order_serial),
+    GNUNET_PQ_result_spec_absolute_time ("creation_time",
+                                         timestamp),
+    GNUNET_PQ_result_spec_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_order_summary",
+                                                   params,
+                                                   rs);
+}
+
+
+/**
+ * Context used for postgres_lookup_orders().
  */
-struct FindContractsContext
+struct LookupOrdersContext
 {
   /**
-   * Function to call on each result.
+   * Function to call with the results.
    */
-  TALER_MERCHANTDB_ProposalDataCallback cb;
+  TALER_MERCHANTDB_OrdersCallback cb;
 
   /**
-   * Closure for @e cb.
+   * Closure for @a cb.
    */
   void *cb_cls;
 
   /**
-   * Transaction status code to set.
+   * Internal result.
    */
   enum GNUNET_DB_QueryStatus qs;
 };
@@ -817,31 +1214,31 @@ struct FindContractsContext
 
 /**
  * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
+ * that has returned @a num_results results about orders.
  *
- * @param cls of type `struct FindContractsContext *`
+ * @param[in,out] cls of type `struct LookupOrdersContext *`
  * @param result the postgres result
  * @param num_result the number of results in @a result
  */
 static void
-find_contracts_cb (void *cls,
-                   PGresult *result,
-                   unsigned int num_results)
+lookup_orders_cb (void *cls,
+                  PGresult *result,
+                  unsigned int num_results)
 {
-  struct FindContractsContext *fcctx = cls;
+  struct LookupOrdersContext *plc = cls;
 
   for (unsigned int i = 0; i < num_results; i++)
   {
     char *order_id;
-    json_t *contract_terms;
-    uint64_t row_id;
+    uint64_t order_serial;
+    struct GNUNET_TIME_Absolute ts;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_string ("order_id",
                                     &order_id),
-      TALER_PQ_result_spec_json ("contract_terms",
-                                 &contract_terms),
-      GNUNET_PQ_result_spec_uint64 ("row_id",
-                                    &row_id),
+      GNUNET_PQ_result_spec_uint64 ("order_serial",
+                                    &order_serial),
+      GNUNET_PQ_result_spec_absolute_time ("creation_time",
+                                           &ts),
       GNUNET_PQ_result_spec_end
     };
 
@@ -851,442 +1248,349 @@ find_contracts_cb (void *cls,
                                   i))
     {
       GNUNET_break (0);
-      fcctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      plc->qs = GNUNET_DB_STATUS_HARD_ERROR;
       return;
     }
-    fcctx->qs = i + 1;
-    fcctx->cb (fcctx->cb_cls,
-               order_id,
-               row_id,
-               contract_terms);
+    plc->cb (plc->cb_cls,
+             order_id,
+             order_serial,
+             ts);
     GNUNET_PQ_cleanup_result (rs);
   }
 }
 
 
 /**
- * Return proposals whose timestamp are older than `date`.
- * Among those proposals, only those ones being between the
- * start-th and (start-nrows)-th record are returned.  The rows
- * are sorted having the youngest first.
+ * Retrieve orders given the @a instance_id.
  *
- * @param cls our plugin handle.
- * @param date only results older than this date are returned.
- * @param merchant_pub instance's public key; only rows related to this
- * instance are returned.
- * @param start only rows with serial id less than start are returned.
- * In other words, you lower `start` to get older records. The tipical
- * usage is to firstly call `find_contract_terms_by_date`, so that you get
- * the `nrows` youngest records. The oldest of those records will tell you
- * from which timestamp and `start` you can query the DB in order to get
- * 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 past if set to #GNUNET_YES, retrieves rows older than `date`.
- * @param ascending if #GNUNET_YES, results will be sorted in chronological 
order.
- * This is typically used to show live updates on the merchant's backoffice
- * Web interface.
- * @param cb function to call with transaction data, can be NULL.
- * @param cb_cls closure for @a cb
+ * @param cls closure
+ * @param instance_id instance to obtain order of
+ * @param of filter to apply when looking up orders
+ * @param[out] contract_terms where to store the retrieved contract terms,
+ *             NULL to only test if the order exists
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_contract_terms_by_date_and_range (void *cls,
-                                                struct GNUNET_TIME_Absolute
-                                                date,
-                                                const struct
-                                                TALER_MerchantPublicKeyP *
-                                                merchant_pub,
-                                                uint64_t start,
-                                                uint64_t nrows,
-                                                int past,
-                                                unsigned int ascending,
-                                                
TALER_MERCHANTDB_ProposalDataCallback
-                                                cb,
-                                                void *cb_cls)
+postgres_lookup_orders (void *cls,
+                        const char *instance_id,
+                        const struct TALER_MERCHANTDB_OrderFilter *of,
+                        TALER_MERCHANTDB_OrdersCallback cb,
+                        void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
+  struct LookupOrdersContext plc = {
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  uint64_t limit = (of->delta > 0) ? of->delta : -of->delta;
+  uint8_t paid;
+  uint8_t refunded;
+  uint8_t wired;
   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_string (instance_id),
+    GNUNET_PQ_query_param_uint64 (&limit),
+    GNUNET_PQ_query_param_uint64 (&of->start_row),
+    GNUNET_PQ_query_param_absolute_time (&of->date),
+    GNUNET_PQ_query_param_auto_from_type (&paid),
+    GNUNET_PQ_query_param_auto_from_type (&refunded),
+    GNUNET_PQ_query_param_auto_from_type (&wired),
     GNUNET_PQ_query_param_end
   };
-  const char *stmt;
   enum GNUNET_DB_QueryStatus qs;
-  struct FindContractsContext fcctx = {
-    .cb = cb,
-    .cb_cls = cb_cls
-  };
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "DB serving /history with date %s\n",
-              GNUNET_STRINGS_absolute_time_to_string (date));
-  stmt =
-    (GNUNET_YES == past)
-    ? ( (GNUNET_YES == ascending)
-        ? "find_contract_terms_by_date_and_range_past_asc"
-        : "find_contract_terms_by_date_and_range_past")
-    : ( (GNUNET_YES == ascending)
-        ? "find_contract_terms_by_date_and_range_asc"
-        : "find_contract_terms_by_date_and_range");
-  check_connection (pg);
+  char stmt[128];
+
+  paid = (TALER_EXCHANGE_YNA_YES == of->paid);
+  refunded = (TALER_EXCHANGE_YNA_YES == of->refunded);
+  wired = (TALER_EXCHANGE_YNA_YES == of->wired);
+  /* painfully many cases..., note that "_xxx" being present in 'stmt' merely
+     means that we filter by that variable, the value we filter for is
+     computed above */
+  GNUNET_snprintf (stmt,
+                   sizeof (stmt),
+                   "lookup_orders_%s%s%s%s",
+                   (of->delta > 0) ? "inc" : "dec",
+                   (TALER_EXCHANGE_YNA_ALL == of->paid) ? "" : "_paid",
+                   (TALER_EXCHANGE_YNA_ALL == of->refunded) ? "" :
+                   "_refunded",
+                   (TALER_EXCHANGE_YNA_ALL == of->wired) ? "" : "_wired");
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
                                              stmt,
                                              params,
-                                             &find_contracts_cb,
-                                             &fcctx);
-  if (0 >= qs)
-    return qs;
-  return fcctx.qs;
+                                             &lookup_orders_cb,
+                                             &plc);
+  if (0 != plc.qs)
+    return plc.qs;
+  return qs;
 }
 
 
 /**
- * Closure for #find_tip_authorizations_cb().
+ * Insert order into the DB.
+ *
+ * @param cls closure
+ * @param instance_id identifies the instance responsible for the order
+ * @param order_id alphanumeric string that uniquely identifies the proposal
+ * @param pay_deadline how long does the customer have to pay for the order
+ * @param contract_terms proposal data to store
+ * @return transaction status
  */
-struct GetAuthorizedTipAmountContext
-{
-  /**
-   * Total authorized amount.
-   */
-  struct TALER_Amount authorized_amount;
-
-  /**
-   * Transaction status code to set.
-   */
-  enum GNUNET_DB_QueryStatus qs;
-
-  /**
-   * Plugin context.
-   */
-  struct PostgresClosure *pg;
+static enum GNUNET_DB_QueryStatus
+postgres_insert_order (void *cls,
+                       const char *instance_id,
+                       const char *order_id,
+                       struct GNUNET_TIME_Absolute pay_deadline,
+                       const json_t *contract_terms)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
+    GNUNET_PQ_query_param_absolute_time (&pay_deadline),
+    GNUNET_PQ_query_param_absolute_time (&now),
+    TALER_PQ_query_param_json (contract_terms),
+    GNUNET_PQ_query_param_end
+  };
 
-};
+  now = GNUNET_TIME_absolute_get ();
+  (void) GNUNET_TIME_round_abs (&now);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "inserting order: order_id: %s, instance_id: %s.\n",
+              order_id,
+              instance_id);
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_order",
+                                             params);
+}
 
 
 /**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
+ * Release an inventory lock by UUID. Releases ALL stocks locked under
+ * the given UUID.
  *
- * @param cls of type `struct GetAuthorizedTipAmountContext *`
- * @param result the postgres result
- * @param num_result the number of results in @a result
+ * @param cls closure
+ * @param uuid the UUID to release locks for
+ * @return transaction status,
+ *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a 
uuid
+ *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
  */
-static void
-find_tip_authorizations_cb (void *cls,
-                            PGresult *result,
-                            unsigned int num_results)
+static enum GNUNET_DB_QueryStatus
+postgres_unlock_inventory (void *cls,
+                           const struct GNUNET_Uuid *uuid)
 {
-  struct GetAuthorizedTipAmountContext *ctx = cls;
-  struct PostgresClosure *pg = ctx->pg;
-  unsigned int i;
-
-  for (i = 0; i < num_results; i++)
-  {
-    struct TALER_Amount amount;
-    char *just;
-    json_t *extra;
-    struct GNUNET_HashCode h;
-    struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_string ("justification",
-                                    &just),
-      GNUNET_PQ_result_spec_auto_from_type ("tip_id",
-                                            &h),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
-                                   &amount),
-      TALER_PQ_result_spec_json ("extra",
-                                 &extra),
-      GNUNET_PQ_result_spec_end
-    };
-
-    if (GNUNET_OK !=
-        GNUNET_PQ_extract_result (result,
-                                  rs,
-                                  i))
-    {
-      GNUNET_break (0);
-      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
-      return;
-    }
-
-    if (0 == i)
-    {
-      ctx->authorized_amount = amount;
-    }
-    else
-    {
-      if (0 >
-          TALER_amount_add (&ctx->authorized_amount,
-                            &ctx->authorized_amount,
-                            &amount))
-      {
-        GNUNET_break (0);
-        GNUNET_PQ_cleanup_result (rs);
-        ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
-        return;
-      }
-    }
-    GNUNET_PQ_cleanup_result (rs);
-  }
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (uuid),
+    GNUNET_PQ_query_param_end
+  };
 
-  if (0 == i)
-  {
-    ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
-  }
-  else
-  {
-    /* one aggregated result */
-    ctx->qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-  }
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "unlock_inventory",
+                                             params);
 }
 
 
 /**
- * Get the total amount of authorized tips for a tipping reserve.
+ * Lock inventory stock to a particular order.
  *
- * @param cls closure, typically a connection to the db
- * @param reserve_priv which reserve to check
- * @param[out] authorzed_amount amount we've authorized so far for tips
- * @return transaction status, usually
- *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
- *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the reserve_priv
- *      does not identify a known tipping reserve
+ * @param cls closure
+ * @param instance_id identifies the instance responsible for the order
+ * @param order_id alphanumeric string that uniquely identifies the order
+ * @param product_id uniquely identifies the product to be locked
+ * @param quantity how many units should be locked to the @a order_id
+ * @return transaction status,
+ *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks
+ *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
  */
 static enum GNUNET_DB_QueryStatus
-postgres_get_authorized_tip_amount (void *cls,
-                                    const struct
-                                    TALER_ReservePrivateKeyP *reserve_priv,
-                                    struct TALER_Amount *authorized_amount)
+postgres_insert_order_lock (void *cls,
+                            const char *instance_id,
+                            const char *order_id,
+                            const char *product_id,
+                            uint64_t quantity)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
+    GNUNET_PQ_query_param_string (product_id),
+    GNUNET_PQ_query_param_uint64 (&quantity),
     GNUNET_PQ_query_param_end
   };
-  enum GNUNET_DB_QueryStatus qs;
-  struct GetAuthorizedTipAmountContext ctx = {
-    .pg = pg
-  };
 
   check_connection (pg);
-  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "find_tip_authorizations",
-                                             params,
-                                             &find_tip_authorizations_cb,
-                                             &ctx);
-  if (0 >= qs)
-    return qs;
-  *authorized_amount = ctx.authorized_amount;
-  return ctx.qs;
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_order_lock",
+                                             params);
 }
 
 
 /**
- * Return proposals whose timestamp are older than `date`.
- * The rows are sorted having the youngest first.
+ * Retrieve contract terms given its @a order_id
  *
- * @param cls our plugin handle.
- * @param date only results older than this date are returned.
- * @param merchant_pub instance's public key; only rows related to this
- * instance are returned.
- * @param nrows at most nrows rows are returned.
- * @param cb function to call with transaction data, can be NULL.
- * @param cb_cls closure for @a cb
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to lookup.
+ * @param[out] contract_terms where to store the result, NULL to only check 
for existence
+ * @param[out] order_serial set to the order's serial number
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_contract_terms_by_date (void *cls,
-                                      struct GNUNET_TIME_Absolute date,
-                                      const struct
-                                      TALER_MerchantPublicKeyP *merchant_pub,
-                                      uint64_t nrows,
-                                      TALER_MERCHANTDB_ProposalDataCallback cb,
-                                      void *cb_cls)
+postgres_lookup_contract_terms (void *cls,
+                                const char *instance_id,
+                                const char *order_id,
+                                json_t **contract_terms,
+                                uint64_t *order_serial)
 {
   struct PostgresClosure *pg = cls;
   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_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
     GNUNET_PQ_query_param_end
   };
-  enum GNUNET_DB_QueryStatus qs;
-  struct FindContractsContext fcctx = {
-    .cb = cb,
-    .cb_cls = cb_cls
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    /* contract_terms must be first! */
+    TALER_PQ_result_spec_json ("contract_terms",
+                               contract_terms),
+    GNUNET_PQ_result_spec_uint64 ("order_serial",
+                                  order_serial),
+    GNUNET_PQ_result_spec_end
   };
 
   check_connection (pg);
-  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "find_contract_terms_by_date",
-                                             params,
-                                             &find_contracts_cb,
-                                             &fcctx);
-  if (0 >= qs)
-    return qs;
-  return fcctx.qs;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_contract_terms",
+                                                   params,
+                                                   (NULL != contract_terms)
+                                                   ? rs
+                                                   : &rs[1]);
 }
 
 
 /**
- * Closure for #find_payments_cb().
- */
-struct FindPaymentsContext
-{
-  /**
-   * Function to call with results.
-   */
-  TALER_MERCHANTDB_CoinDepositCallback cb;
-
-  /**
-   * Closure for @e cls.
-   */
-  void *cb_cls;
-
-  /**
-   * Plugin context.
-   */
-  struct PostgresClosure *pg;
-
-  /**
-   * Contract term hash used for the search.
-   */
-  const struct GNUNET_HashCode *h_contract_terms;
-
-  /**
-   * Transaction status (set).
-   */
-  enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
+ * Store contract terms given its @a order_id. Note that some attributes are
+ * expected to be calculated inside of the function, like the hash of the
+ * contract terms (to be hashed), the creation_time and pay_deadline (to be
+ * obtained from the merchant_orders table). The "session_id" should be
+ * initially set to the empty string.  The "fulfillment_url" and 
"refund_deadline"
+ * must be extracted from @a contract_terms.
  *
- * @param cls of type `struct FindPaymentsContext *`
- * @param result the postgres result
- * @param num_result the number of results in @a result
+ * @param cls closure
+ * @param instance_id instance's identifier
+ * @param order_id order_id used to store
+ * @param contract_terms contract to store
+ * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a 
contract_terms
+ *          is malformed
  */
-static void
-find_payments_cb (void *cls,
-                  PGresult *result,
-                  unsigned int num_results)
+static enum GNUNET_DB_QueryStatus
+postgres_insert_contract_terms (void *cls,
+                                const char *instance_id,
+                                const char *order_id,
+                                json_t *contract_terms)
 {
-  struct FindPaymentsContext *fpc = cls;
-  struct PostgresClosure *pg = fpc->pg;
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute pay_deadline;
+  struct GNUNET_TIME_Absolute refund_deadline;
+  const char *fulfillment_url;
+  struct GNUNET_HashCode h_contract_terms;
 
-  for (unsigned int i = 0; i<num_results; i++)
+  if (GNUNET_OK !=
+      TALER_JSON_hash (contract_terms,
+                       &h_contract_terms))
   {
-    struct TALER_CoinSpendPublicKeyP coin_pub;
-    struct TALER_Amount amount_with_fee;
-    struct TALER_Amount deposit_fee;
-    struct TALER_Amount refund_fee;
-    struct TALER_Amount wire_fee;
-    json_t *exchange_proof;
-    char *exchange_url;
-    struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
-                                            &coin_pub),
-      GNUNET_PQ_result_spec_string ("exchange_url",
-                                    &exchange_url),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
-                                   &amount_with_fee),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
-                                   &deposit_fee),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee",
-                                   &refund_fee),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
-                                   &wire_fee),
-      TALER_PQ_result_spec_json ("exchange_proof",
-                                 &exchange_proof),
-      GNUNET_PQ_result_spec_end
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  {
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("fulfillment_url",
+                               &fulfillment_url),
+      GNUNET_JSON_spec_absolute_time ("pay_deadline",
+                                      &pay_deadline),
+      GNUNET_JSON_spec_absolute_time ("refund_deadline",
+                                      &refund_deadline),
+      GNUNET_JSON_spec_end ()
     };
+    enum GNUNET_GenericReturnValue res;
 
-    if (GNUNET_OK !=
-        GNUNET_PQ_extract_result (result,
-                                  rs,
-                                  i))
+    res = TALER_MHD_parse_json_data (NULL,
+                                     contract_terms,
+                                     spec);
+    if (GNUNET_OK != res)
     {
       GNUNET_break (0);
-      fpc->qs = GNUNET_DB_STATUS_HARD_ERROR;
-      return;
+      return GNUNET_DB_STATUS_HARD_ERROR;
     }
-    fpc->qs = i + 1;
-    fpc->cb (fpc->cb_cls,
-             fpc->h_contract_terms,
-             &coin_pub,
-             exchange_url,
-             &amount_with_fee,
-             &deposit_fee,
-             &refund_fee,
-             &wire_fee,
-             exchange_proof);
-    GNUNET_PQ_cleanup_result (rs);
+  }
+
+  check_connection (pg);
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_string (order_id),
+      TALER_PQ_query_param_json (contract_terms),
+      GNUNET_PQ_query_param_auto_from_type (&h_contract_terms),
+      GNUNET_PQ_query_param_absolute_time (&pay_deadline),
+      GNUNET_PQ_query_param_absolute_time (&refund_deadline),
+      GNUNET_PQ_query_param_string (fulfillment_url),
+      GNUNET_PQ_query_param_end
+    };
+
+    return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                               "insert_contract_terms",
+                                               params);
   }
 }
 
 
 /**
- * Lookup information about coin payments by proposal data hash
- * (and @a merchant_pub)
+ * Delete information about a contract. Note that the transaction must
+ * enforce that the contract is not awaiting payment anymore AND was not
+ * paid, or is past the legal expiration.
  *
  * @param cls closure
- * @param h_contract_terms key for the search
- * @param merchant_pub merchant's public key
- * @param cb function to call with payment data
- * @param cb_cls closure for @a cb
- * @return transaction status
+ * @param instance_id instance to delete order of
+ * @param order_id order to delete
+ * @param legal_expiration how long do we need to keep (paid) contracts on
+ *          file for legal reasons (i.e. taxation)
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ *           if locks prevent deletion OR order unknown
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_payments (void *cls,
-                        const struct GNUNET_HashCode *h_contract_terms,
-                        const struct TALER_MerchantPublicKeyP *merchant_pub,
-                        TALER_MERCHANTDB_CoinDepositCallback cb,
-                        void *cb_cls)
+postgres_delete_contract_terms (void *cls,
+                                const char *instance_id,
+                                const char *order_id,
+                                struct GNUNET_TIME_Relative legal_expiration)
 {
   struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
+    GNUNET_PQ_query_param_relative_time (&legal_expiration),
+    GNUNET_PQ_query_param_absolute_time (&now),
     GNUNET_PQ_query_param_end
   };
-  struct FindPaymentsContext fpc = {
-    .h_contract_terms = h_contract_terms,
-    .cb = cb,
-    .cb_cls = cb_cls,
-    .pg = pg
-  };
-  enum GNUNET_DB_QueryStatus qs;
 
-  /* no preflight check here, run in its own transaction by the
-     caller! */
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Finding payment for h_contract_terms '%s'\n",
-              GNUNET_h2s (h_contract_terms));
   check_connection (pg);
-  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "find_deposits",
-                                             params,
-                                             &find_payments_cb,
-                                             &fpc);
-  if (qs <= 0)
-    return qs;
-  return fpc.qs;
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "delete_contract_terms",
+                                             params);
 }
 
 
 /**
- * Closure for #find_payments_by_coin_cb().
+ * Closure for #lookup_deposits_cb().
  */
-struct FindPaymentsByCoinContext
+struct LookupDepositsContext
 {
   /**
    * Function to call with results.
    */
-  TALER_MERCHANTDB_CoinDepositCallback cb;
+  TALER_MERCHANTDB_DepositsCallback cb;
 
   /**
    * Closure for @e cls.
@@ -1298,16 +1602,6 @@ struct FindPaymentsByCoinContext
    */
   struct PostgresClosure *pg;
 
-  /**
-   * Coin we are looking for.
-   */
-  const struct TALER_CoinSpendPublicKeyP *coin_pub;
-
-  /**
-   * Hash of the contract we are looking for.
-   */
-  const struct GNUNET_HashCode *h_contract_terms;
-
   /**
    * Transaction status (set).
    */
@@ -1319,27 +1613,31 @@ struct FindPaymentsByCoinContext
  * Function to be called with the results of a SELECT statement
  * that has returned @a num_results results.
  *
- * @param cls of type `struct FindPaymentsByCoinContext *`
+ * @param[in,out] cls of type `struct LookupDepositsContext *`
  * @param result the postgres result
  * @param num_result the number of results in @a result
  */
 static void
-find_payments_by_coin_cb (void *cls,
-                          PGresult *result,
-                          unsigned int num_results)
+lookup_deposits_cb (void *cls,
+                    PGresult *result,
+                    unsigned int num_results)
 {
-  struct FindPaymentsByCoinContext *fpc = cls;
-  struct PostgresClosure *pg = fpc->pg;
+  struct LookupDepositsContext *ldc = cls;
+  struct PostgresClosure *pg = ldc->pg;
 
   for (unsigned int i = 0; i<num_results; i++)
   {
+    struct TALER_CoinSpendPublicKeyP coin_pub;
     struct TALER_Amount amount_with_fee;
     struct TALER_Amount deposit_fee;
     struct TALER_Amount refund_fee;
     struct TALER_Amount wire_fee;
     char *exchange_url;
-    json_t *exchange_proof;
     struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
+      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+                                            &coin_pub),
       TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                    &amount_with_fee),
       TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
@@ -1348,10 +1646,6 @@ find_payments_by_coin_cb (void *cls,
                                    &refund_fee),
       TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
                                    &wire_fee),
-      GNUNET_PQ_result_spec_string ("exchange_url",
-                                    &exchange_url),
-      TALER_PQ_result_spec_json ("exchange_proof",
-                                 &exchange_proof),
       GNUNET_PQ_result_spec_end
     };
 
@@ -1361,341 +1655,179 @@ find_payments_by_coin_cb (void *cls,
                                   i))
     {
       GNUNET_break (0);
-      fpc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      ldc->qs = GNUNET_DB_STATUS_HARD_ERROR;
       return;
     }
-    fpc->qs = i + 1;
-    fpc->cb (fpc->cb_cls,
-             fpc->h_contract_terms,
-             fpc->coin_pub,
+    ldc->qs = i + 1;
+    ldc->cb (ldc->cb_cls,
              exchange_url,
+             &coin_pub,
              &amount_with_fee,
              &deposit_fee,
              &refund_fee,
-             &wire_fee,
-             exchange_proof);
+             &wire_fee);
     GNUNET_PQ_cleanup_result (rs);
   }
 }
 
 
 /**
- * Retrieve information about a deposited coin.
+ * Lookup information about coins that were successfully deposited for a
+ * given contract.
  *
  * @param cls closure
- * @param h_contract_terms hashcode of the proposal data paid by @a coin_pub
- * @param merchant_pub merchant's public key.
- * @param coin_pub coin's public key used for the search
+ * @param instance_id instance to lookup deposits for
+ * @param h_contract_terms proposal data's hashcode
  * @param cb function to call with payment data
  * @param cb_cls closure for @a cb
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_payments_by_hash_and_coin (void *cls,
-                                         const struct
-                                         GNUNET_HashCode *h_contract_terms,
-                                         const struct
-                                         TALER_MerchantPublicKeyP 
*merchant_pub,
-                                         const struct
-                                         TALER_CoinSpendPublicKeyP *coin_pub,
-                                         TALER_MERCHANTDB_CoinDepositCallback 
cb,
-                                         void *cb_cls)
+postgres_lookup_deposits (void *cls,
+                          const char *instance_id,
+                          const struct GNUNET_HashCode *h_contract_terms,
+                          TALER_MERCHANTDB_DepositsCallback cb,
+                          void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
     GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
     GNUNET_PQ_query_param_end
   };
-  struct FindPaymentsByCoinContext fpc = {
+  struct LookupDepositsContext ldc = {
     .cb = cb,
     .cb_cls = cb_cls,
-    .pg = pg,
-    .h_contract_terms = h_contract_terms,
-    .coin_pub = coin_pub
+    .pg = pg
   };
   enum GNUNET_DB_QueryStatus qs;
 
+  /* no preflight check here, run in its own transaction by the caller! */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Finding deposits for h_contract_terms '%s'\n",
+              GNUNET_h2s (h_contract_terms));
   check_connection (pg);
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "find_deposits_by_hash_and_coin",
+                                             "lookup_deposits",
                                              params,
-                                             &find_payments_by_coin_cb,
-                                             &fpc);
-  if (0 >= qs)
+                                             &lookup_deposits_cb,
+                                             &ldc);
+  if (qs <= 0)
     return qs;
-  return fpc.qs;
+  return ldc.qs;
 }
 
 
 /**
- * Closure for #find_transfers_cb().
- */
-struct FindTransfersContext
-{
-  /**
-   * Function to call on results.
-   */
-  TALER_MERCHANTDB_TransferCallback cb;
-
-  /**
-   * Closure for @e cb.
-   */
-  void *cb_cls;
-
-  /**
-   * Hash of the contract we are looking under.
-   */
-  const struct GNUNET_HashCode *h_contract_terms;
-
-  /**
-   * Transaction status (set).
-   */
-  enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct FindTransfersContext *`
- * @param result the postgres result
- * @param num_result the number of results in @a result
- */
-static void
-find_transfers_cb (void *cls,
-                   PGresult *result,
-                   unsigned int num_results)
-{
-  struct FindTransfersContext *ftc = cls;
-
-  for (unsigned int i = 0; i<num_results; i++)
-  {
-    struct TALER_CoinSpendPublicKeyP coin_pub;
-    struct TALER_WireTransferIdentifierRawP wtid;
-    struct GNUNET_TIME_Absolute execution_time;
-    json_t *proof;
-
-    struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
-                                            &coin_pub),
-      GNUNET_PQ_result_spec_auto_from_type ("wtid",
-                                            &wtid),
-      GNUNET_PQ_result_spec_absolute_time ("execution_time",
-                                           &execution_time),
-      TALER_PQ_result_spec_json ("proof",
-                                 &proof),
-      GNUNET_PQ_result_spec_end
-    };
-
-    if (GNUNET_OK !=
-        GNUNET_PQ_extract_result (result,
-                                  rs,
-                                  i))
-    {
-      GNUNET_break (0);
-      ftc->qs = GNUNET_DB_STATUS_HARD_ERROR;
-      return;
-    }
-    ftc->qs = i + 1;
-    ftc->cb (ftc->cb_cls,
-             ftc->h_contract_terms,
-             &coin_pub,
-             &wtid,
-             execution_time,
-             proof);
-    GNUNET_PQ_cleanup_result (rs);
-  }
-}
-
-
-/**
- * Lookup information about a transfer by @a h_contract_terms.  Note
- * that in theory there could be multiple wire transfers for a
- * single @a h_contract_terms, as the transaction may have involved
- * multiple coins and the coins may be spread over different wire
- * transfers.
- *
- * @param cls closure
- * @param h_contract_terms key for the search
- * @param cb function to call with transfer data
- * @param cb_cls closure for @a cb
- * @return transaction status
+ * Insert an exchange signing key into our database.
+ *
+ * @param cls closure
+ * @param master_pub exchange master public key used for @a master_sig
+ * @param exchange_pub exchange signing key to insert
+ * @param start_date when does the signing key become valid
+ * @param expire_date when does the signing key stop being used
+ * @param end_date when does the signing key become void as proof
+ * @param master_sig signature of @a master_pub over the @a exchange_pub and 
the dates
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_transfers_by_hash (void *cls,
-                                 const struct GNUNET_HashCode 
*h_contract_terms,
-                                 TALER_MERCHANTDB_TransferCallback cb,
-                                 void *cb_cls)
+postgres_insert_exchange_signkey (
+  void *cls,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct GNUNET_TIME_Absolute start_date,
+  struct GNUNET_TIME_Absolute expire_date,
+  struct GNUNET_TIME_Absolute end_date,
+  const struct TALER_MasterSignatureP *master_sig)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+    GNUNET_PQ_query_param_absolute_time (&start_date),
+    GNUNET_PQ_query_param_absolute_time (&expire_date),
+    GNUNET_PQ_query_param_absolute_time (&end_date),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
     GNUNET_PQ_query_param_end
   };
-  struct FindTransfersContext ftc = {
-    .h_contract_terms = h_contract_terms,
-    .cb = cb,
-    .cb_cls = cb_cls
-  };
-  enum GNUNET_DB_QueryStatus qs;
 
   check_connection (pg);
-  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "find_transfers_by_hash",
-                                             params,
-                                             &find_transfers_cb,
-                                             &ftc);
-  if (0 >= qs)
-    return qs;
-  return ftc.qs;
-}
-
-
-/**
- * Closure for #find_deposits_cb().
- */
-struct FindDepositsContext
-{
-
-  /**
-   * Function to call for each result.
-   */
-  TALER_MERCHANTDB_CoinDepositCallback cb;
-
-  /**
-   * Closure for @e cb.
-   */
-  void *cb_cls;
-
-  /**
-   * Plugin context.
-   */
-  struct PostgresClosure *pg;
-
-  /**
-   * Transaction status (set).
-   */
-  enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls of type `struct FindDepositsContext *`
- * @param result the postgres result
- * @param num_result the number of results in @a result
- */
-static void
-find_deposits_cb (void *cls,
-                  PGresult *result,
-                  unsigned int num_results)
-{
-  struct FindDepositsContext *fdc = cls;
-  struct PostgresClosure *pg = fdc->pg;
-
-  for (unsigned int i = 0; i<num_results; i++)
-  {
-    struct GNUNET_HashCode h_contract_terms;
-    struct TALER_CoinSpendPublicKeyP coin_pub;
-    struct TALER_Amount amount_with_fee;
-    struct TALER_Amount deposit_fee;
-    struct TALER_Amount refund_fee;
-    struct TALER_Amount wire_fee;
-    char *exchange_url;
-    json_t *exchange_proof;
-    struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
-                                            &h_contract_terms),
-      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
-                                            &coin_pub),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
-                                   &amount_with_fee),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
-                                   &deposit_fee),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee",
-                                   &refund_fee),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
-                                   &wire_fee),
-      GNUNET_PQ_result_spec_string ("exchange_url",
-                                    &exchange_url),
-      TALER_PQ_result_spec_json ("exchange_proof",
-                                 &exchange_proof),
-      GNUNET_PQ_result_spec_end
-    };
+  postgres_preflight (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_exchange_signkey",
+                                             params);
 
-    if (GNUNET_OK !=
-        GNUNET_PQ_extract_result (result,
-                                  rs,
-                                  i))
-    {
-      GNUNET_break (0);
-      fdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
-      return;
-    }
-    fdc->qs = i + 1;
-    fdc->cb (fdc->cb_cls,
-             &h_contract_terms,
-             &coin_pub,
-             exchange_url,
-             &amount_with_fee,
-             &deposit_fee,
-             &refund_fee,
-             &wire_fee,
-             exchange_proof);
-    GNUNET_PQ_cleanup_result (rs);
-  }
 }
 
 
 /**
- * Lookup information about a coin deposits by @a wtid.
+ * Insert payment confirmation from the exchange into the database.
  *
  * @param cls closure
- * @param wtid wire transfer identifier to find matching transactions for
- * @param cb function to call with payment data
- * @param cb_cls closure for @a cb
+ * @param instance_id instance to lookup deposits for
+ * @param deposit_timestamp time when the exchange generated the deposit 
confirmation
+ * @param h_contract_terms proposal data's hashcode
+ * @param coin_pub public key of the coin
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param wire_fee wire fee the exchange charges
+ * @param h_wire hash of the wire details of the target account of the merchant
+ * @param exchange_timestamp timestamp from the exchange
+ * @param exchange_sig signature from exchange that coin was accepted
+ * @param exchange_pub signgin key that was used for @a exchange_sig
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_deposits_by_wtid (void *cls,
-                                const struct
-                                TALER_WireTransferIdentifierRawP *wtid,
-                                TALER_MERCHANTDB_CoinDepositCallback cb,
-                                void *cb_cls)
+postgres_insert_deposit (void *cls,
+                         const char *instance_id,
+                         struct GNUNET_TIME_Absolute deposit_timestamp,
+                         const struct GNUNET_HashCode *h_contract_terms,
+                         const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                         const char *exchange_url,
+                         const struct TALER_Amount *amount_with_fee,
+                         const struct TALER_Amount *deposit_fee,
+                         const struct TALER_Amount *refund_fee,
+                         const struct TALER_Amount *wire_fee,
+                         const struct GNUNET_HashCode *h_wire,
+                         const struct TALER_ExchangeSignatureP *exchange_sig,
+                         const struct TALER_ExchangePublicKeyP *exchange_pub)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (wtid),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+    GNUNET_PQ_query_param_absolute_time (&deposit_timestamp), /* $3 */
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_string (exchange_url),
+    TALER_PQ_query_param_amount (amount_with_fee), /* $6/$7 */
+    TALER_PQ_query_param_amount (deposit_fee),  /* $8, $9 */
+    TALER_PQ_query_param_amount (refund_fee), /* $10, $11 */
+    TALER_PQ_query_param_amount (wire_fee),  /* $12, $13 */
+    GNUNET_PQ_query_param_auto_from_type (h_wire), /* $14 */
+    GNUNET_PQ_query_param_auto_from_type (exchange_sig), /* $15 */
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub), /* $16 */
     GNUNET_PQ_query_param_end
   };
-  struct FindDepositsContext fdc = {
-    .cb = cb,
-    .cb_cls = cb_cls,
-    .pg = pg
-  };
-  enum GNUNET_DB_QueryStatus qs;
 
+  /* no preflight check here, run in transaction by caller! */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Storing deposit for instance `%s' h_contract_terms `%s', 
coin_pub: `%s', amount_with_fee: %s\n",
+              instance_id,
+              GNUNET_h2s (h_contract_terms),
+              TALER_B2S (coin_pub),
+              TALER_amount2s (amount_with_fee));
   check_connection (pg);
-  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "find_deposits_by_wtid",
-                                             params,
-                                             &find_deposits_cb,
-                                             &fdc);
-  if (0 >= qs)
-    return qs;
-  return fdc.qs;
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_deposit",
+                                             params);
+
 }
 
 
 /**
- * Closure for #get_refunds_cb().
+ * Closure for #lookup_refunds_cb().
  */
-struct GetRefundsContext
+struct LookupRefundsContext
 {
   /**
    * Function to call for each refund.
@@ -1723,39 +1855,27 @@ struct GetRefundsContext
  * Function to be called with the results of a SELECT statement
  * that has returned @a num_results results.
  *
- * @param cls of type `struct GetRefundsContext *`
+ * @param cls of type `struct LookupRefundsContext *`
  * @param result the postgres result
  * @param num_result the number of results in @a result
  */
 static void
-get_refunds_cb (void *cls,
-                PGresult *result,
-                unsigned int num_results)
+lookup_refunds_cb (void *cls,
+                   PGresult *result,
+                   unsigned int num_results)
 {
-  struct GetRefundsContext *grc = cls;
-  struct PostgresClosure *pg = grc->pg;
+  struct LookupRefundsContext *lrc = cls;
+  struct PostgresClosure *pg = lrc->pg;
 
   for (unsigned int i = 0; i<num_results; i++)
   {
     struct TALER_CoinSpendPublicKeyP coin_pub;
-    uint64_t rtransaction_id;
     struct TALER_Amount refund_amount;
-    struct TALER_Amount refund_fee;
-    char *reason;
-    char *exchange_url;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
                                             &coin_pub),
-      GNUNET_PQ_result_spec_string ("exchange_url",
-                                    &exchange_url),
-      GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
-                                    &rtransaction_id),
       TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
                                    &refund_amount),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee",
-                                   &refund_fee),
-      GNUNET_PQ_result_spec_string ("reason",
-                                    &reason),
       GNUNET_PQ_result_spec_end
     };
 
@@ -1765,18 +1885,14 @@ get_refunds_cb (void *cls,
                                   i))
     {
       GNUNET_break (0);
-      grc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
       return;
     }
-    grc->qs = i + 1;
-    grc->rc (grc->rc_cls,
+    lrc->qs = i + 1;
+    lrc->rc (lrc->rc_cls,
              &coin_pub,
-             exchange_url,
-             rtransaction_id,
-             reason,
-             &refund_amount,
-             &refund_fee);
-    GNUNET_PQ_cleanup_result (rs);
+             &refund_amount);
+    GNUNET_PQ_cleanup_result (rs); /* technically useless here */
   }
 }
 
@@ -1785,27 +1901,26 @@ get_refunds_cb (void *cls,
  * Obtain refunds associated with a contract.
  *
  * @param cls closure, typically a connection to the db
- * @param merchant_pub public key of the merchant instance
+ * @param instance_id instance to lookup refunds for
  * @param h_contract_terms hash code of the contract
  * @param rc function to call for each coin on which there is a refund
  * @param rc_cls closure for @a rc
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_get_refunds_from_contract_terms_hash (
-  void *cls,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const struct GNUNET_HashCode *h_contract_terms,
-  TALER_MERCHANTDB_RefundCallback rc,
-  void *rc_cls)
+postgres_lookup_refunds (void *cls,
+                         const char *instance_id,
+                         const struct GNUNET_HashCode *h_contract_terms,
+                         TALER_MERCHANTDB_RefundCallback rc,
+                         void *rc_cls)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+    GNUNET_PQ_query_param_string (instance_id),
     GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
     GNUNET_PQ_query_param_end
   };
-  struct GetRefundsContext grc = {
+  struct LookupRefundsContext lrc = {
     .rc = rc,
     .rc_cls = rc_cls,
     .pg = pg
@@ -1813,266 +1928,230 @@ postgres_get_refunds_from_contract_terms_hash (
   enum GNUNET_DB_QueryStatus qs;
 
   /* no preflight check here, run in transaction by caller! */
-  TALER_LOG_DEBUG ("Looking for refund %s + %s\n",
+  TALER_LOG_DEBUG ("Looking for refund of h_contract_terms %s at `%s'\n",
                    GNUNET_h2s (h_contract_terms),
-                   TALER_B2S (merchant_pub));
+                   instance_id);
   check_connection (pg);
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             
"find_refunds_from_contract_terms_hash",
+                                             "lookup_refunds",
                                              params,
-                                             &get_refunds_cb,
-                                             &grc);
+                                             &lookup_refunds_cb,
+                                             &lrc);
   if (0 >= qs)
     return qs;
-  return grc.qs;
+  return lrc.qs;
 }
 
 
 /**
- * Obtain refund proofs associated with a refund operation on a
- * coin.
+ * Mark contract as paid and store the current @a session_id
+ * for which the contract was paid. Deletes the underlying order
+ * and marks the locked stocks of the order as sold.
  *
- * @param cls closure, typically a connection to the db
- * @param merchant_pub public key of the merchant instance
- * @param h_contract_terms hash code of the contract
- * @param coin_pub public key of the coin
- * @param rtransaction_id identificator of the refund
- * @param[out] exchange_pub public key of the exchange affirming the refund
- * @param[out] exchange_sig signature of the exchange affirming the refund
+ * @param cls closure
+ * @param instance_id instance to mark contract as paid for
+ * @param h_contract_terms hash of the contract that is now paid
+ * @param session_id the session that paid the contract, can be NULL
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_get_refund_proof (
-  void *cls,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const struct GNUNET_HashCode *h_contract_terms,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  uint64_t rtransaction_id,
-  struct TALER_ExchangePublicKeyP *exchange_pub,
-  struct TALER_ExchangeSignatureP *exchange_sig)
+postgres_mark_contract_paid (void *cls,
+                             const char *instance_id,
+                             const struct GNUNET_HashCode *h_contract_terms,
+                             const char *session_id)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
     GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
-    GNUNET_PQ_query_param_uint64 (&rtransaction_id),
+    GNUNET_PQ_query_param_string (session_id),
     GNUNET_PQ_query_param_end
   };
-  struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
-                                          exchange_sig),
-    GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
-                                          exchange_pub),
-    GNUNET_PQ_result_spec_end
+  struct GNUNET_PQ_QueryParam uparams[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+    GNUNET_PQ_query_param_end
   };
+  enum GNUNET_DB_QueryStatus qs;
 
-  check_connection (pg);
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "get_refund_proof",
-                                                   params,
-                                                   rs);
+  /* no preflight check here, run in transaction by caller! */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Marking h_contract_terms '%s' of %s as paid for session `%s'\n",
+              GNUNET_h2s (h_contract_terms),
+              instance_id,
+              session_id);
+  qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                           "mark_contract_paid",
+                                           params);
+  if (qs <= 0)
+    return qs;
+  qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                           "mark_inventory_sold",
+                                           uparams);
+  if (qs < 0)
+    return qs; /* 0: no inventory management, that's OK! */
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "delete_completed_order",
+                                             uparams);
 }
 
 
 /**
- * Store refund proofs associated with a refund operation on a
- * coin.
+ * Function called during aborts to refund a coin. Marks the
+ * respective coin as refunded.
  *
- * @param cls closure, typically a connection to the db
- * @param merchant_pub public key of the merchant instance
- * @param h_contract_terms hash code of the contract
- * @param coin_pub public key of the coin
- * @param rtransaction_id identificator of the refund
- * @param exchange_pub public key of the exchange affirming the refund
- * @param exchange_sig signature of the exchange affirming the refund
+ * @param cls closure
+ * @param instance_id instance to refund payment for
+ * @param h_contract_terms hash of the contract to refund coin for
+ * @param coin_pub public key of the coin to refund (fully)
+ * @param reason text justifying the refund
  * @return transaction status
+ *        #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to us;
+ *        #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
+ *        regardless of whether it actually increased the refund
  */
 static enum GNUNET_DB_QueryStatus
-postgres_put_refund_proof (
-  void *cls,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const struct GNUNET_HashCode *h_contract_terms,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  uint64_t rtransaction_id,
-  const struct TALER_ExchangePublicKeyP *exchange_pub,
-  const struct TALER_ExchangeSignatureP *exchange_sig)
-{
-  struct PostgresClosure *pg = cls;
-  struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_uint64 (&rtransaction_id),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
-    GNUNET_PQ_query_param_auto_from_type (exchange_sig),
-    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
-    GNUNET_PQ_query_param_end
-  };
-
-  TALER_LOG_DEBUG ("Inserting refund proof %s + %s\n",
-                   GNUNET_h2s (h_contract_terms),
-                   TALER_B2S (coin_pub));
-  check_connection (pg);
-  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_refund_proof",
-                                             params);
-}
-
-
-/**
- * Insert a refund row into merchant_refunds.  Not meant to be exported
- * in the db API.
- *
- * @param cls closure, typically a connection to the db
- * @param merchant_pub merchant instance public key
- * @param h_contract_terms hashcode of the contract related to this refund
- * @param coin_pub public key of the coin giving the (part of) refund
- * @param reason human readable explanation behind the refund
- * @param refund how much this coin is refunding
- */
-static enum GNUNET_DB_QueryStatus
-insert_refund (void *cls,
-               const struct TALER_MerchantPublicKeyP *merchant_pub,
-               const struct GNUNET_HashCode *h_contract_terms,
-               const struct TALER_CoinSpendPublicKeyP *coin_pub,
-               const char *reason,
-               const struct TALER_Amount *refund)
+postgres_refund_coin (void *cls,
+                      const char *instance_id,
+                      const struct GNUNET_HashCode *h_contract_terms,
+                      struct GNUNET_TIME_Absolute refund_timestamp,
+                      const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                      const char *reason)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+    GNUNET_PQ_query_param_string (instance_id),
     GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+    GNUNET_PQ_query_param_absolute_time (&refund_timestamp),
     GNUNET_PQ_query_param_auto_from_type (coin_pub),
     GNUNET_PQ_query_param_string (reason),
-    TALER_PQ_query_param_amount (refund),
     GNUNET_PQ_query_param_end
   };
 
-  TALER_LOG_DEBUG ("Inserting refund %s + %s\n",
-                   GNUNET_h2s (h_contract_terms),
-                   TALER_B2S (merchant_pub));
-
-  check_connection (pg);
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_refund",
+                                             "refund_coin",
                                              params);
 }
 
 
 /**
- * Store information about wire fees charged by an exchange,
- * including signature (so we have proof).
+ * Retrieve contract terms given its @a order_id
  *
  * @param cls closure
- * @paramm exchange_pub public key of the exchange
- * @param h_wire_method hash of wire method
- * @param wire_fee wire fee charged
- * @param closing_fee closing fee charged (irrelevant for us,
- *              but needed to check signature)
- * @param start_date start of fee being used
- * @param end_date end of fee being used
- * @param exchange_sig signature of exchange over fee structure
- * @return transaction status code
+ * @param instance_id instance's identifier
+ * @param order_id order to lookup contract for
+ * @param[out] h_contract_terms set to the hash of the contract.
+ * @param[out] paid set to the payment status of the contract
+ * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_store_wire_fee_by_exchange (
-  void *cls,
-  const struct
-  TALER_MasterPublicKeyP *exchange_pub,
-  const struct
-  GNUNET_HashCode *h_wire_method,
-  const struct TALER_Amount *wire_fee,
-  const struct TALER_Amount *closing_fee,
-  struct GNUNET_TIME_Absolute start_date,
-  struct GNUNET_TIME_Absolute end_date,
-  const struct
-  TALER_MasterSignatureP *exchange_sig)
+postgres_lookup_order_status (void *cls,
+                              const char *instance_id,
+                              const char *order_id,
+                              struct GNUNET_HashCode *h_contract_terms,
+                              bool *paid)
 {
   struct PostgresClosure *pg = cls;
+  uint8_t paid8;
+  enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
-    GNUNET_PQ_query_param_auto_from_type (h_wire_method),
-    TALER_PQ_query_param_amount (wire_fee),
-    TALER_PQ_query_param_amount (closing_fee),
-    GNUNET_PQ_query_param_absolute_time (&start_date),
-    GNUNET_PQ_query_param_absolute_time (&end_date),
-    GNUNET_PQ_query_param_auto_from_type (exchange_sig),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
     GNUNET_PQ_query_param_end
   };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+                                          h_contract_terms),
+    GNUNET_PQ_result_spec_auto_from_type ("paid",
+                                          &paid8),
+    GNUNET_PQ_result_spec_end
+  };
 
-  /* no preflight check here, run in its own transaction by the caller */
   check_connection (pg);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Storing wire fee for %s starting at %s of %s\n",
-              TALER_B2S (exchange_pub),
-              GNUNET_STRINGS_absolute_time_to_string (start_date),
-              TALER_amount2s (wire_fee));
-  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_wire_fee",
-                                             params);
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "lookup_order_status",
+                                                 params,
+                                                 rs);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    *paid = (0 != paid8);
+  else
+    *paid = false; /* just to be safe(r) */
+  return qs;
 }
 
 
 /**
- * Obtain information about wire fees charged by an exchange,
- * including signature (so we have proof).
+ * Retrieve payment and wire status for a given @a order_serial and session ID.
  *
  * @param cls closure
- * @param exchange_pub public key of the exchange
- * @param h_wire_method hash of wire method
- * @param contract_date date of the contract to use for the lookup
- * @param[out] wire_fee wire fee charged
- * @param[out] closing_fee closing fee charged (irrelevant for us,
- *              but needed to check signature)
- * @param[out] start_date start of fee being used
- * @param[out] end_date end of fee being used
- * @param[out] exchange_sig signature of exchange over fee structure
- * @return transaction status code
+ * @param order_serial identifies the order
+ * @param session_id session for which to check the payment status, NULL for 
any
+ * @param[out] paid set to the payment status of the contract
+ * @param[out] wired set to the wire transfer status of the exchange payment
+ * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_lookup_wire_fee (void *cls,
-                          const struct TALER_MasterPublicKeyP *exchange_pub,
-                          const struct GNUNET_HashCode *h_wire_method,
-                          struct GNUNET_TIME_Absolute contract_date,
-                          struct TALER_Amount *wire_fee,
-                          struct TALER_Amount *closing_fee,
-                          struct GNUNET_TIME_Absolute *start_date,
-                          struct GNUNET_TIME_Absolute *end_date,
-                          struct TALER_MasterSignatureP *exchange_sig)
+postgres_lookup_payment_status (void *cls,
+                                uint64_t order_serial,
+                                const char *session_id,
+                                bool *paid,
+                                bool *wired)
 {
   struct PostgresClosure *pg = cls;
-  struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
-    GNUNET_PQ_query_param_auto_from_type (h_wire_method),
-    GNUNET_PQ_query_param_absolute_time (&contract_date),
-    GNUNET_PQ_query_param_end
-  };
+  uint8_t paid8;
+  uint8_t wired8;
+  enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_PQ_ResultSpec rs[] = {
-    TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
-                                 wire_fee),
-    TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
-                                 closing_fee),
-    GNUNET_PQ_result_spec_absolute_time ("start_date",
-                                         start_date),
-    GNUNET_PQ_result_spec_absolute_time ("end_date",
-                                         end_date),
-    GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
-                                          exchange_sig),
+    GNUNET_PQ_result_spec_auto_from_type ("paid",
+                                          &paid8),
+    GNUNET_PQ_result_spec_auto_from_type ("wired",
+                                          &wired8),
     GNUNET_PQ_result_spec_end
   };
-
   check_connection (pg);
-  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "lookup_wire_fee",
+  if (NULL == session_id)
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_uint64 (&order_serial),
+      GNUNET_PQ_query_param_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_payment_status",
+                                                   params,
+                                                   rs);
+  }
+  else
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_uint64 (&order_serial),
+      GNUNET_PQ_query_param_string (session_id),
+      GNUNET_PQ_query_param_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   
"lookup_payment_status_session_id",
                                                    params,
                                                    rs);
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  {
+    *paid = (0 != paid8);
+    *wired = (0 != wired8);
+  }
+  else
+  {
+    *paid = false; /* just to be safe(r) */
+    *wired = false; /* just to be safe(r) */
+  }
+  return qs;
 }
 
 
 /**
- * Closure for #process_refund_cb.
+ * Closure for lookup_deposits_by_order_cb().
  */
-struct FindRefundContext
+struct LookupDepositsByOrderContext
 {
 
   /**
@@ -2081,14 +2160,19 @@ struct FindRefundContext
   struct PostgresClosure *pg;
 
   /**
-   * Updated to reflect total amount refunded so far.
+   * Function to call with all results.
    */
-  struct TALER_Amount refunded_amount;
+  TALER_MERCHANTDB_DepositedCoinsCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
 
   /**
-   * Set to #GNUNET_SYSERR on hard errors.
+   * Set to the query result.
    */
-  int err;
+  enum GNUNET_DB_QueryStatus qs;
 };
 
 
@@ -2096,25 +2180,39 @@ struct FindRefundContext
  * Function to be called with the results of a SELECT statement
  * that has returned @a num_results results.
  *
- * @param cls closure, our `struct FindRefundContext`
+ * @param cls of type `struct LookupDepositsByOrderContext *`
  * @param result the postgres result
  * @param num_result the number of results in @a result
  */
 static void
-process_refund_cb (void *cls,
-                   PGresult *result,
-                   unsigned int num_results)
+lookup_deposits_by_order_cb (void *cls,
+                             PGresult *result,
+                             unsigned int num_results)
 {
-  struct FindRefundContext *ictx = cls;
-  struct PostgresClosure *pg = ictx->pg;
+  struct LookupDepositsByOrderContext *ldoc = cls;
+  struct PostgresClosure *pg = ldoc->pg;
 
   for (unsigned int i = 0; i<num_results; i++)
   {
-    /* Sum up existing refunds */
-    struct TALER_Amount acc;
+    uint64_t deposit_serial;
+    char *exchange_url;
+    struct GNUNET_HashCode h_wire;
+    struct TALER_CoinSpendPublicKeyP coin_pub;
+    struct TALER_Amount amount_with_fee;
+    struct TALER_Amount deposit_fee;
     struct GNUNET_PQ_ResultSpec rs[] = {
-      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
-                                   &acc),
+      GNUNET_PQ_result_spec_uint64 ("deposit_serial",
+                                    &deposit_serial),
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
+      GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+                                            &h_wire),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                   &amount_with_fee),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
+                                   &deposit_fee),
+      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+                                            &coin_pub),
       GNUNET_PQ_result_spec_end
     };
 
@@ -2124,57 +2222,83 @@ process_refund_cb (void *cls,
                                   i))
     {
       GNUNET_break (0);
-      ictx->err = GNUNET_SYSERR;
-      return;
-    }
-    if (0 >
-        TALER_amount_add (&ictx->refunded_amount,
-                          &ictx->refunded_amount,
-                          &acc))
-    {
-      GNUNET_break (0);
-      ictx->err = GNUNET_SYSERR;
+      ldoc->qs = GNUNET_DB_STATUS_HARD_ERROR;
       return;
     }
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Found refund of %s\n",
-                TALER_amount2s (&acc));
+    ldoc->qs = i + 1;
+    ldoc->cb (ldoc->cb_cls,
+              deposit_serial,
+              exchange_url,
+              &h_wire,
+              &amount_with_fee,
+              &deposit_fee,
+              &coin_pub);
+    GNUNET_PQ_cleanup_result (rs); /* technically useless here */
   }
 }
 
 
 /**
- * Closure for #process_deposits_for_refund_cb.
+ * Retrieve details about coins that were deposited for an order.
+ *
+ * @param cls closure
+ * @param order_serial identifies the order
+ * @param cb function to call for each deposited coin
+ * @param cb_cls closure for @a cb
+ * @return transaction status
  */
-struct InsertRefundContext
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_deposits_by_order (void *cls,
+                                   uint64_t order_serial,
+                                   TALER_MERCHANTDB_DepositedCoinsCallback cb,
+                                   void *cb_cls)
 {
-  /**
-   * Used to provide a connection to the db
-   */
-  struct PostgresClosure *pg;
+  struct PostgresClosure *pg = cls;
+  struct LookupDepositsByOrderContext ldoc = {
+    .pg = pg,
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&order_serial),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_deposits_by_order",
+                                             params,
+                                             &lookup_deposits_by_order_cb,
+                                             &ldoc);
+  if (qs < 0)
+    return qs;
+  return ldoc.qs;
+}
 
-  /**
-   * Amount to which increase the refund for this contract
-   */
-  const struct TALER_Amount *refund;
+
+/**
+ * Closure for lookup_deposits_by_order_cb().
+ */
+struct LookupTransferDetailsByOrderContext
+{
 
   /**
-   * Merchant instance public key
+   * Plugin context.
    */
-  const struct TALER_MerchantPublicKeyP *merchant_pub;
+  struct PostgresClosure *pg;
 
   /**
-   * Hash code representing the contract
+   * Function to call with all results.
    */
-  const struct GNUNET_HashCode *h_contract_terms;
+  TALER_MERCHANTDB_OrderTransferDetailsCallback cb;
 
   /**
-   * Human-readable reason behind this refund
+   * Closure for @e cb.
    */
-  const char *reason;
+  void *cb_cls;
 
   /**
-   * Transaction status code.
+   * Set to the query result.
    */
   enum GNUNET_DB_QueryStatus qs;
 };
@@ -2184,48 +2308,42 @@ struct InsertRefundContext
  * Function to be called with the results of a SELECT statement
  * that has returned @a num_results results.
  *
- * @param cls closure, our `struct InsertRefundContext`
+ * @param cls of type `struct LookupTransferDetailsByOrderContext *`
  * @param result the postgres result
  * @param num_result the number of results in @a result
  */
 static void
-process_deposits_for_refund_cb (void *cls,
-                                PGresult *result,
-                                unsigned int num_results)
+lookup_transfer_details_by_order_cb (void *cls,
+                                     PGresult *result,
+                                     unsigned int num_results)
 {
-  struct InsertRefundContext *ctx = cls;
-  struct PostgresClosure *pg = ctx->pg;
-  struct TALER_Amount current_refund;
-  struct TALER_Amount deposit_refund[GNUNET_NZL (num_results)];
-  struct TALER_CoinSpendPublicKeyP deposit_coin_pubs[GNUNET_NZL (num_results)];
-  struct TALER_Amount deposit_amount_with_fee[GNUNET_NZL (num_results)];
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_get_zero (ctx->refund->currency,
-                                        &current_refund));
+  struct LookupTransferDetailsByOrderContext *ltdo = cls;
+  struct PostgresClosure *pg = ltdo->pg;
 
-  /* Pass 1:  Collect amount of existing refunds into current_refund.
-   * Also store existing refunded amount for each deposit in deposit_refund. */
   for (unsigned int i = 0; i<num_results; i++)
   {
-    struct TALER_CoinSpendPublicKeyP coin_pub;
-    struct TALER_Amount amount_with_fee;
+    struct TALER_WireTransferIdentifierRawP wtid;
+    char *exchange_url;
+    uint64_t deposit_serial;
+    struct GNUNET_TIME_Absolute execution_time;
+    struct TALER_Amount deposit_value;
+    struct TALER_Amount deposit_fee;
+    uint8_t transfer_confirmed;
     struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
-                                            &coin_pub),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
-                                   &amount_with_fee),
+      GNUNET_PQ_result_spec_uint64 ("deposit_serial",
+                                    &deposit_serial),
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
+      GNUNET_PQ_result_spec_auto_from_type ("wtid",
+                                            &wtid),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value",
+                                   &deposit_value),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee",
+                                   &deposit_fee),
+      GNUNET_PQ_result_spec_auto_from_type ("transfer_confirmed",
+                                            &transfer_confirmed),
       GNUNET_PQ_result_spec_end
     };
-    struct FindRefundContext ictx = {
-      .err = GNUNET_OK,
-      .pg = pg
-    };
-    enum GNUNET_DB_QueryStatus ires;
-    struct GNUNET_PQ_QueryParam params[] = {
-      GNUNET_PQ_query_param_auto_from_type (&coin_pub),
-      GNUNET_PQ_query_param_end
-    };
 
     if (GNUNET_OK !=
         GNUNET_PQ_extract_result (result,
@@ -2233,44 +2351,356 @@ process_deposits_for_refund_cb (void *cls,
                                   i))
     {
       GNUNET_break (0);
-      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      ltdo->qs = GNUNET_DB_STATUS_HARD_ERROR;
       return;
     }
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (ctx->refund->currency,
-                                          &ictx.refunded_amount));
-    ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn,
-                                                 "find_refunds",
-                                                 params,
-                                                 &process_refund_cb,
-                                                 &ictx);
-    if ( (GNUNET_OK != ictx.err) ||
-         (GNUNET_DB_STATUS_HARD_ERROR == ires) )
-    {
-      GNUNET_break (0);
-      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+    ltdo->qs = i + 1;
+    ltdo->cb (ltdo->cb_cls,
+              &wtid,
+              exchange_url,
+              execution_time,
+              &deposit_value,
+              &deposit_fee,
+              (0 != transfer_confirmed));
+    GNUNET_PQ_cleanup_result (rs); /* technically useless here */
+  }
+}
+
+
+/**
+ * Retrieve wire transfer details for all deposits associated with
+ * a given @a order_serial.
+ *
+ * @param cls closure
+ * @param order_serial identifies the order
+ * @param cb function called with the wire transfer details
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_transfer_details_by_order (
+  void *cls,
+  uint64_t order_serial,
+  TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
+  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct LookupTransferDetailsByOrderContext ltdo = {
+    .pg = pg,
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&order_serial),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = GNUNET_PQ_eval_prepared_multi_select (
+    pg->conn,
+    "lookup_transfer_details_by_order",
+    params,
+    &lookup_transfer_details_by_order_cb,
+    &ltdo);
+  if (qs < 0)
+    return qs;
+  return ltdo.qs;
+}
+
+
+/**
+ * Insert wire transfer details for a deposit.
+ *
+ * @param cls closure
+ * @param deposit_serial serial number of the deposit
+ * @param dd deposit transfer data from the exchange to store
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_insert_deposit_to_transfer (
+  void *cls,
+  uint64_t deposit_serial,
+  const struct TALER_EXCHANGE_DepositData *dd)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&deposit_serial),
+    TALER_PQ_query_param_amount (&dd->coin_contribution),
+    GNUNET_PQ_query_param_absolute_time (&dd->execution_time),
+    GNUNET_PQ_query_param_auto_from_type (&dd->exchange_sig),
+    GNUNET_PQ_query_param_auto_from_type (&dd->exchange_pub),
+    GNUNET_PQ_query_param_auto_from_type (&dd->wtid),
+    GNUNET_PQ_query_param_end
+  };
+
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_deposit_to_transfer",
+                                             params);
+}
+
+
+/**
+ * Set 'wired' status for an order to 'true'.
+ *
+ * @param cls closure
+ * @param order_serial serial number of the order
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_mark_order_wired (void *cls,
+                           uint64_t order_serial)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&order_serial),
+    GNUNET_PQ_query_param_end
+  };
+
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "mark_order_wired",
+                                             params);
+}
+
+
+/**
+ * Closure for #process_refund_cb().
+ */
+struct FindRefundContext
+{
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Updated to reflect total amount refunded so far.
+   */
+  struct TALER_Amount refunded_amount;
+
+  /**
+   * Set to the largest refund transaction ID encountered.
+   */
+  uint64_t max_rtransaction_id;
+
+  /**
+   * Set to true on hard errors.
+   */
+  bool err;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure, our `struct FindRefundContext`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+process_refund_cb (void *cls,
+                   PGresult *result,
+                   unsigned int num_results)
+{
+  struct FindRefundContext *ictx = cls;
+  struct PostgresClosure *pg = ictx->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    /* Sum up existing refunds */
+    struct TALER_Amount acc;
+    uint64_t rtransaction_id;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
+                                   &acc),
+      GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+                                    &rtransaction_id),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ictx->err = true;
+      return;
+    }
+    if (0 >
+        TALER_amount_add (&ictx->refunded_amount,
+                          &ictx->refunded_amount,
+                          &acc))
+    {
+      GNUNET_break (0);
+      ictx->err = true;
       return;
     }
-    if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
+    ictx->max_rtransaction_id = GNUNET_MAX (ictx->max_rtransaction_id,
+                                            rtransaction_id);
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Found refund of %s\n",
+                TALER_amount2s (&acc));
+  }
+}
+
+
+/**
+ * Closure for #process_deposits_for_refund_cb().
+ */
+struct InsertRefundContext
+{
+  /**
+   * Used to provide a connection to the db
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Amount to which increase the refund for this contract
+   */
+  const struct TALER_Amount *refund;
+
+  /**
+   * Human-readable reason behind this refund
+   */
+  const char *reason;
+
+  /**
+   * Transaction status code.
+   */
+  enum TALER_MERCHANTDB_RefundStatus rs;
+};
+
+
+/**
+ * Data extracted per coin.
+ */
+struct RefundCoinData
+{
+
+  /**
+   * Public key of a coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Amount deposited for this coin.
+   */
+  struct TALER_Amount deposited_with_fee;
+
+  /**
+   * Amount refunded already for this coin.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Order serial (actually not really per-coin).
+   */
+  uint64_t order_serial;
+
+  /**
+   * Maximum rtransaction_id for this coin so far.
+   */
+  uint64_t max_rtransaction_id;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure, our `struct InsertRefundContext`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+process_deposits_for_refund_cb (void *cls,
+                                PGresult *result,
+                                unsigned int num_results)
+{
+  struct InsertRefundContext *ctx = cls;
+  struct PostgresClosure *pg = ctx->pg;
+  struct TALER_Amount current_refund;
+  struct RefundCoinData rcd[GNUNET_NZL (num_results)];
+  struct GNUNET_TIME_Absolute now;
+
+  now = GNUNET_TIME_absolute_get ();
+  (void) GNUNET_TIME_round_abs (&now);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_get_zero (ctx->refund->currency,
+                                        &current_refund));
+  memset (rcd, 0, sizeof (rcd));
+  /* Pass 1:  Collect amount of existing refunds into current_refund.
+   * Also store existing refunded amount for each deposit in deposit_refund. */
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+                                            &rcd[i].coin_pub),
+      GNUNET_PQ_result_spec_uint64 ("order_serial",
+                                    &rcd[i].order_serial),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                   &rcd[i].deposited_with_fee),
+      GNUNET_PQ_result_spec_end
+    };
+    struct FindRefundContext ictx = {
+      .pg = pg
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
     {
-      ctx->qs = GNUNET_DB_STATUS_SOFT_ERROR;
+      GNUNET_break (0);
+      ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
       return;
     }
-    deposit_refund[i] = ictx.refunded_amount;
-    deposit_amount_with_fee[i] = amount_with_fee;
-    deposit_coin_pubs[i] = coin_pub;
+
+    {
+      enum GNUNET_DB_QueryStatus ires;
+      struct GNUNET_PQ_QueryParam params[] = {
+        GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
+        GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
+        GNUNET_PQ_query_param_end
+      };
+
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_amount_get_zero (ctx->refund->currency,
+                                            &ictx.refunded_amount));
+      ires = GNUNET_PQ_eval_prepared_multi_select (ctx->pg->conn,
+                                                   "find_refunds_by_coin",
+                                                   params,
+                                                   &process_refund_cb,
+                                                   &ictx);
+      if ( (ictx.err) ||
+           (GNUNET_DB_STATUS_HARD_ERROR == ires) )
+      {
+        GNUNET_break (0);
+        ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
+        return;
+      }
+      if (GNUNET_DB_STATUS_SOFT_ERROR == ires)
+      {
+        ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
+        return;
+      }
+    }
     if (0 >
         TALER_amount_add (&current_refund,
                           &current_refund,
                           &ictx.refunded_amount))
     {
       GNUNET_break (0);
-      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
       return;
     }
+    rcd[i].refund_amount = ictx.refunded_amount;
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Existing refund for coin %s is %s\n",
-                TALER_B2S (&coin_pub),
+                TALER_B2S (&rcd[i].coin_pub),
                 TALER_amount2s (&ictx.refunded_amount));
   }
 
@@ -2286,6 +2716,7 @@ process_deposits_for_refund_cb (void *cls,
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Existing refund of %s at or above requested refund. Finished 
early.\n",
                 TALER_amount2s (&current_refund));
+    ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
     return;
   }
 
@@ -2299,11 +2730,11 @@ process_deposits_for_refund_cb (void *cls,
     /* How much of the coin is left after the existing refunds? */
     if (0 >
         TALER_amount_subtract (&left,
-                               &deposit_amount_with_fee[i],
-                               &deposit_refund[i]))
+                               &rcd[i].deposited_with_fee,
+                               &rcd[i].refund_amount))
     {
       GNUNET_break (0);
-      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
       return;
     }
 
@@ -2313,10 +2744,11 @@ process_deposits_for_refund_cb (void *cls,
       /* coin was fully refunded, move to next coin */
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                   "Coin %s fully refunded, moving to next coin\n",
-                  TALER_B2S (&deposit_coin_pubs[i]));
+                  TALER_B2S (&rcd[i].coin_pub));
       continue;
     }
 
+    rcd[i].max_rtransaction_id++;
     /* How much of the refund is still to be paid back? */
     if (0 >
         TALER_amount_subtract (&remaining_refund,
@@ -2324,7 +2756,7 @@ process_deposits_for_refund_cb (void *cls,
                                &current_refund))
     {
       GNUNET_break (0);
-      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
       return;
     }
 
@@ -2346,40 +2778,57 @@ process_deposits_for_refund_cb (void *cls,
                           increment))
     {
       GNUNET_break (0);
-      ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
       return;
     }
 
     /* actually run the refund */
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Coin %s deposit amount is %s\n",
-                TALER_B2S (&deposit_coin_pubs[i]),
-                TALER_amount2s (&deposit_amount_with_fee[i]));
+                TALER_B2S (&rcd[i].coin_pub),
+                TALER_amount2s (&rcd[i].deposited_with_fee));
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Coin %s refund will be incremented by %s\n",
-                TALER_B2S (&deposit_coin_pubs[i]),
+                TALER_B2S (&rcd[i].coin_pub),
                 TALER_amount2s (increment));
     {
       enum GNUNET_DB_QueryStatus qs;
+      struct GNUNET_PQ_QueryParam params[] = {
+        GNUNET_PQ_query_param_uint64 (&rcd[i].order_serial),
+        GNUNET_PQ_query_param_uint64 (&rcd[i].max_rtransaction_id), /* already 
inc'ed */
+        GNUNET_PQ_query_param_absolute_time (&now),
+        GNUNET_PQ_query_param_auto_from_type (&rcd[i].coin_pub),
+        GNUNET_PQ_query_param_string (ctx->reason),
+        TALER_PQ_query_param_amount (increment),
+        GNUNET_PQ_query_param_end
+      };
 
-      if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          (qs = insert_refund (ctx->pg,
-                               ctx->merchant_pub,
-                               ctx->h_contract_terms,
-                               &deposit_coin_pubs[i],
-                               ctx->reason,
-                               increment)))
+      check_connection (pg);
+      qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                               "insert_refund",
+                                               params);
+      switch (qs)
       {
-        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
-        ctx->qs = qs;
+      case GNUNET_DB_STATUS_HARD_ERROR:
+        GNUNET_break (0);
+        ctx->rs = TALER_MERCHANTDB_RS_HARD_ERROR;
         return;
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        ctx->rs = TALER_MERCHANTDB_RS_SOFT_ERROR;
+        return;
+      default:
+        ctx->rs = qs;
+        break;
       }
     }
 
     /* stop immediately if we are done */
     if (0 == TALER_amount_cmp (ctx->refund,
                                &current_refund))
+    {
+      ctx->rs = TALER_MERCHANTDB_RS_SUCCESS;
       return;
+    }
   }
 
   /**
@@ -2391,7 +2840,7 @@ process_deposits_for_refund_cb (void *cls,
   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
               "The refund of %s is bigger than the order's value\n",
               TALER_amount2s (ctx->refund));
-  ctx->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  ctx->rs = TALER_MERCHANTDB_RS_TOO_HIGH;
 }
 
 
@@ -2399,11 +2848,11 @@ process_deposits_for_refund_cb (void *cls,
  * Function called when some backoffice staff decides to award or
  * increase the refund on an existing contract.  This function
  * MUST be called from within a transaction scope setup by the
- * caller as it executes multiple SQL statements (NT).
+ * caller as it executes multiple SQL statements.
  *
  * @param cls closure
- * @param h_contract_terms
- * @param merchant_pub merchant's instance public key
+ * @param instance_id instance identifier
+ * @param order_id the order to increase the refund for
  * @param refund maximum refund to return to the customer for this contract
  * @param reason 0-terminated UTF-8 string giving the reason why the customer
  *               got a refund (free form, business-specific)
@@ -2414,36 +2863,32 @@ process_deposits_for_refund_cb (void *cls,
  *        regardless of whether it actually increased the refund beyond
  *        what was already refunded (idempotency!)
  */
-static enum GNUNET_DB_QueryStatus
-postgres_increase_refund_for_contract_NT (
-  void *cls,
-  const struct GNUNET_HashCode *h_contract_terms,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const struct TALER_Amount *refund,
-  const char *reason)
+static enum TALER_MERCHANTDB_RefundStatus
+postgres_increase_refund (void *cls,
+                          const char *instance_id,
+                          const char *order_id,
+                          const struct TALER_Amount *refund,
+                          const char *reason)
 {
   struct PostgresClosure *pg = cls;
   enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (order_id),
     GNUNET_PQ_query_param_end
   };
   struct InsertRefundContext ctx = {
     .pg = pg,
-    .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
     .refund = refund,
-    .reason = reason,
-    .h_contract_terms = h_contract_terms,
-    .merchant_pub = merchant_pub
+    .reason = reason
   };
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Asked to refund %s on contract %s\n",
+              "Asked to refund %s on order %s\n",
               TALER_amount2s (refund),
-              GNUNET_h2s (h_contract_terms));
+              order_id);
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "find_deposits",
+                                             "find_deposits_for_refund",
                                              params,
                                              &process_deposits_for_refund_cb,
                                              &ctx);
@@ -2451,94 +2896,366 @@ postgres_increase_refund_for_contract_NT (
   {
   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     /* never paid, means we clearly cannot refund anything */
-    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+    return TALER_MERCHANTDB_RS_NO_SUCH_ORDER;
   case GNUNET_DB_STATUS_SOFT_ERROR:
+    return TALER_MERCHANTDB_RS_SOFT_ERROR;
   case GNUNET_DB_STATUS_HARD_ERROR:
-    return qs;
+    return TALER_MERCHANTDB_RS_HARD_ERROR;
   default:
     /* Got one or more deposits */
-    return ctx.qs;
+    return ctx.rs;
   }
 }
 
 
 /**
- * Lookup proof information about a wire transfer.
- *
- * @param cls closure
- * @param exchange_url from which exchange are we looking for proof
- * @param wtid wire transfer identifier for the search
- * @param cb function to call with proof data
- * @param cb_cls closure for @a cb
+ * Closure for #lookup_refunds_detailed_cb().
+ */
+struct LookupRefundsDetailedContext
+{
+  /**
+   * Function to call for each refund.
+   */
+  TALER_MERCHANTDB_RefundDetailCallback rc;
+
+  /**
+   * Closure for @e rc.
+   */
+  void *rc_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Transaction result.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct GetRefundsContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_refunds_detailed_cb (void *cls,
+                            PGresult *result,
+                            unsigned int num_results)
+{
+  struct LookupRefundsDetailedContext *lrdc = cls;
+  struct PostgresClosure *pg = lrdc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    uint64_t refund_serial;
+    struct GNUNET_TIME_Absolute timestamp;
+    struct TALER_CoinSpendPublicKeyP coin_pub;
+    uint64_t rtransaction_id;
+    struct TALER_Amount refund_amount;
+    char *reason;
+    char *exchange_url;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("refund_serial",
+                                    &refund_serial),
+      GNUNET_PQ_result_spec_absolute_time ("refund_timestamp",
+                                           &timestamp),
+      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+                                            &coin_pub),
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
+      GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+                                    &rtransaction_id),
+      GNUNET_PQ_result_spec_string ("reason",
+                                    &reason),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
+                                   &refund_amount),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      lrdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    lrdc->qs = i + 1;
+    lrdc->rc (lrdc->rc_cls,
+              refund_serial,
+              timestamp,
+              &coin_pub,
+              exchange_url,
+              rtransaction_id,
+              reason,
+              &refund_amount);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Obtain detailed refund data associated with a contract.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id instance to lookup refunds for
+ * @param h_contract_terms hash code of the contract
+ * @param rc function to call for each coin on which there is a refund
+ * @param rc_cls closure for @a rc
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_find_proof_by_wtid (void *cls,
-                             const char *exchange_url,
-                             const struct
-                             TALER_WireTransferIdentifierRawP *wtid,
-                             TALER_MERCHANTDB_ProofCallback cb,
-                             void *cb_cls)
+postgres_lookup_refunds_detailed (
+  void *cls,
+  const char *instance_id,
+  const struct GNUNET_HashCode *h_contract_terms,
+  TALER_MERCHANTDB_RefundDetailCallback rc,
+  void *rc_cls)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (wtid),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+    GNUNET_PQ_query_param_end
+  };
+  struct LookupRefundsDetailedContext lrdc = {
+    .rc  = rc,
+    .rc_cls = rc_cls,
+    .pg = pg
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  /* no preflight check here, run in transaction by caller! */
+  TALER_LOG_DEBUG ("Looking for refund %s + %s\n",
+                   GNUNET_h2s (h_contract_terms),
+                   instance_id);
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_refunds_detailed",
+                                             params,
+                                             &lookup_refunds_detailed_cb,
+                                             &lrdc);
+  if (0 >= qs)
+    return qs;
+  return lrdc.qs;
+}
+
+
+/**
+ * Insert refund proof data from the exchange into the database.
+ *
+ * @param cls closure
+ * @param refund_serial serial number of the refund
+ * @param refund_fee refund fee the exchange said it charged
+ * @param exchange_sig signature from exchange that coin was refunded
+ * @param exchange_pub signing key that was used for @a exchange_sig
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_insert_refund_proof (
+  void *cls,
+  uint64_t refund_serial,
+  const struct TALER_Amount *refund_fee,
+  const struct TALER_ExchangeSignatureP *exchange_sig,
+  const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&refund_serial),
+    GNUNET_PQ_query_param_auto_from_type (exchange_sig),
+    TALER_PQ_query_param_amount (refund_fee),
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+    GNUNET_PQ_query_param_end
+  };
+
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_refund_proof",
+                                             params);
+}
+
+
+/**
+ * Lookup refund proof data.
+ *
+ * @param cls closure
+ * @param refund_serial serial number of the refund
+ * @param[out] exchange_sig set to signature from exchange
+ * @param[out] exchange_pub signing key that was used for @a exchange_sig
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_refund_proof (void *cls,
+                              uint64_t refund_serial,
+                              struct TALER_ExchangeSignatureP *exchange_sig,
+                              struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&refund_serial),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
+                                          exchange_sig),
+    GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+                                          exchange_pub),
+    GNUNET_PQ_result_spec_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_refund_proof",
+                                                   params,
+                                                   rs);
+}
+
+
+/**
+ * Retrieve the order ID that was used to pay for a resource within a session.
+ *
+ * @param cls closure
+ * @param instance_id identifying the instance
+ * @param fulfillment_url URL that canonically identifies the resource
+ *        being paid for
+ * @param session_id session id
+ * @param[out] order_id where to store the order ID that was used when
+ *             paying for the resource URL
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_lookup_order_by_fulfillment (void *cls,
+                                      const char *instance_id,
+                                      const char *fulfillment_url,
+                                      const char *session_id,
+                                      char **order_id)
+{
+  struct PostgresClosure *pg = cls;
+
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (fulfillment_url),
+    GNUNET_PQ_query_param_string (session_id),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_string ("order_id",
+                                  order_id),
+    GNUNET_PQ_result_spec_end
+  };
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   
"lookup_order_by_fulfillment",
+                                                   params,
+                                                   rs);
+}
+
+
+/**
+ * Insert information about a wire transfer the merchant has received.
+ *
+ * @param cls closure
+ * @param instance_id the instance that received the transfer
+ * @param exchange_url which exchange made the transfer
+ * @param wtid identifier of the wire transfer
+ * @param credit_amount how much did we receive
+ * @param payto_uri what is the merchant's bank account that received the 
transfer
+ * @param confirmed whether the transfer was confirmed by the merchant or
+ *                  was merely claimed by the exchange at this point
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_insert_transfer (
+  void *cls,
+  const char *instance_id,
+  const char *exchange_url,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_Amount *credit_amount,
+  const char *payto_uri,
+  bool confirmed)
+{
+  struct PostgresClosure *pg = cls;
+  uint8_t confirmed8 = confirmed;
+  struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_string (exchange_url),
+    GNUNET_PQ_query_param_auto_from_type (wtid),
+    TALER_PQ_query_param_amount (credit_amount),
+    GNUNET_PQ_query_param_string (payto_uri),
+    GNUNET_PQ_query_param_auto_from_type (&confirmed8),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_transfer",
+                                             params);
+}
+
+
+/**
+ * Lookup account serial by payto URI.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup the account from
+ * @param payto_uri what is the merchant's bank account to lookup
+ * @param[out] account_serial serial number of the account
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_account (void *cls,
+                         const char *instance_id,
+                         const char *payto_uri,
+                         uint64_t *account_serial)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_string (payto_uri),
     GNUNET_PQ_query_param_end
   };
-  json_t *proof;
   struct GNUNET_PQ_ResultSpec rs[] = {
-    TALER_PQ_result_spec_json ("proof",
-                               &proof),
+    GNUNET_PQ_result_spec_uint64 ("account_serial",
+                                  account_serial),
     GNUNET_PQ_result_spec_end
   };
-  enum GNUNET_DB_QueryStatus qs;
 
   check_connection (pg);
-  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "find_proof_by_wtid",
-                                                 params,
-                                                 rs);
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
-  {
-    cb (cb_cls,
-        proof);
-    GNUNET_PQ_cleanup_result (rs);
-  }
-  return qs;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_account",
+                                                   params,
+                                                   rs);
 }
 
 
 /**
- * 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.
+ * Insert information about a wire transfer the merchant has received.
  *
- * @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
+ * @param cls closure
+ * @param instance_id instance to provide transfer details for
+ * @param exchange_url which exchange made the transfer
+ * @param payto_uri what is the merchant's bank account that received the 
transfer
+ * @param wtid identifier of the wire transfer
+ * @param td transfer details to store
+ * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_enable_tip_reserve_TR (void *cls,
-                                const struct
-                                TALER_ReservePrivateKeyP *reserve_priv,
-                                const struct GNUNET_HashCode *credit_uuid,
-                                const struct TALER_Amount *credit,
-                                struct GNUNET_TIME_Absolute expiration)
+postgres_insert_transfer_details (
+  void *cls,
+  const char *instance_id,
+  const char *exchange_url,
+  const char *payto_uri,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_EXCHANGE_TransferData *td)
 {
   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;
+  uint64_t credit_serial;
   unsigned int retries;
 
   retries = 0;
@@ -2547,26 +3264,31 @@ RETRY:
   if (MAX_RETRIES < ++retries)
     return GNUNET_DB_STATUS_SOFT_ERROR;
   if (GNUNET_OK !=
-      postgres_start (pg,
-                      "enable tip reserve"))
+      postgres_start_read_committed (pg,
+                                     "insert transfer details"))
   {
     GNUNET_break (0);
     return GNUNET_DB_STATUS_HARD_ERROR;
   }
 
-  /* ensure that credit_uuid is new/unique */
+  /* lookup credit serial */
   {
     struct GNUNET_PQ_QueryParam params[] = {
-      GNUNET_PQ_query_param_auto_from_type (credit_uuid),
-      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_string (exchange_url),
+      GNUNET_PQ_query_param_string (payto_uri),
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (wtid),
+      TALER_PQ_query_param_amount (&td->total_amount), /* excludes wire fee */
       GNUNET_PQ_query_param_end
     };
-
     struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("credit_serial",
+                                    &credit_serial),
       GNUNET_PQ_result_spec_end
     };
+
     qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "lookup_tip_credit_uuid",
+                                                   "lookup_credit_serial",
                                                    params,
                                                    rs);
     if (0 > qs)
@@ -2577,28 +3299,27 @@ RETRY:
         goto RETRY;
       return qs;
     }
-    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     {
-      /* UUID already exists, we are done! */
+      /* account does not exists, fail! */
       postgres_rollback (pg);
       return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     }
   }
 
+  /* update merchant_transfer_signatures table */
   {
-    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_uint64 (&credit_serial),
+      TALER_PQ_query_param_amount (&td->wire_fee),
+      GNUNET_PQ_query_param_absolute_time (&td->execution_time),
+      GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig),
+      GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub),
       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",
+                                             "insert_transfer_signature",
                                              params);
     if (0 > qs)
     {
@@ -2610,75 +3331,47 @@ RETRY:
     }
   }
 
-  /* Obtain existing reserve balance */
+  /* Update transfer-coin association table */
+  for (unsigned int i = 0; i<td->details_length; i++)
   {
+    const struct TALER_TrackTransferDetails *d = &td->details[i];
+    uint64_t i64 = (uint64_t) i;
     struct GNUNET_PQ_QueryParam params[] = {
-      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_uint64 (&credit_serial),
+      GNUNET_PQ_query_param_uint64 (&i64),
+      TALER_PQ_query_param_amount (&d->coin_value),
+      TALER_PQ_query_param_amount (&d->coin_fee), /* deposit fee */
+      GNUNET_PQ_query_param_auto_from_type (&d->coin_pub),
+      GNUNET_PQ_query_param_auto_from_type (&d->h_contract_terms),
       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 (0 >
-        TALER_amount_add (&new_balance,
-                          credit,
-                          &old_balance))
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_transfer_to_coin_mapping",
+                                             params);
+    if (0 > qs)
     {
-      GNUNET_break (0);
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
       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));
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return qs;
     }
-    new_expiration = expiration;
-    new_balance = *credit;
   }
-
+  /* Update merchant_contract_terms 'wired' status: for all coins
+     that were wired, set the respective order's "wired" status to
+     true, *if* all other deposited coins associated with that order
+     have also been wired (this time or earlier) */
+  for (unsigned int i = 0; i<td->details_length; i++)
   {
+    const struct TALER_TrackTransferDetails *d = &td->details[i];
     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_auto_from_type (&d->coin_pub),
       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,
+                                             "update_wired_by_coin_pub",
                                              params);
     if (0 > qs)
     {
@@ -2700,435 +3393,3725 @@ RETRY:
 
 
 /**
- * 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.
+ * Obtain information about wire fees charged by an exchange,
+ * including signature (so we have proof).
  *
- * @param cls closure, typically a connection to the db
- * @param justification why was the tip approved
- * @param extra extra data for the customer's wallet
- * @param amount how high is the tip (with fees)
- * @param reserve_priv which reserve is debited
- * @param exchange_url 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
+ * @param cls closure
+ * @param master_pub public key of the exchange
+ * @param wire_method the wire method
+ * @param contract_date date of the contract to use for the lookup
+ * @param[out] wire_fee wire fee charged
+ * @param[out] closing_fee closing fee charged (irrelevant for us,
+ *              but needed to check signature)
+ * @param[out] start_date start of fee being used
+ * @param[out] end_date end of fee being used
+ * @param[out] master_sig signature of exchange over fee structure
+ * @return transaction status code
  */
-static enum TALER_ErrorCode
-postgres_authorize_tip_TR (void *cls,
-                           const char *justification,
-                           const json_t *extra,
-                           const struct TALER_Amount *amount,
-                           const struct TALER_ReservePrivateKeyP *reserve_priv,
-                           const char *exchange_url,
-                           struct GNUNET_TIME_Absolute *expiration,
-                           struct GNUNET_HashCode *tip_id)
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_wire_fee (void *cls,
+                          const struct TALER_MasterPublicKeyP *master_pub,
+                          const char *wire_method,
+                          struct GNUNET_TIME_Absolute contract_date,
+                          struct TALER_Amount *wire_fee,
+                          struct TALER_Amount *closing_fee,
+                          struct GNUNET_TIME_Absolute *start_date,
+                          struct GNUNET_TIME_Absolute *end_date,
+                          struct TALER_MasterSignatureP *master_sig)
 {
   struct PostgresClosure *pg = cls;
+  struct GNUNET_HashCode h_wire_method;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
+    GNUNET_PQ_query_param_auto_from_type (&h_wire_method),
+    GNUNET_PQ_query_param_absolute_time (&contract_date),
     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),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+                                 wire_fee),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+                                 closing_fee),
+    GNUNET_PQ_result_spec_absolute_time ("start_date",
+                                         start_date),
+    GNUNET_PQ_result_spec_absolute_time ("end_date",
+                                         end_date),
+    GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                          master_sig),
     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,
-                      "authorize tip"))
-  {
-    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_INSUFFICIENT_FUNDS;
-    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 (0 >
-      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
-    };
+  GNUNET_CRYPTO_hash (wire_method,
+                      strlen (wire_method) + 1,
+                      &h_wire_method);
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_wire_fee",
+                                                   params,
+                                                   rs);
+}
 
-    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);
+
+/**
+ * Closure for #lookup_deposits_by_contract_and_coin_cb().
+ */
+struct LookupDepositsByCnCContext
+{
+  /**
+   * Function to call for each deposit.
+   */
+  TALER_MERCHANTDB_CoinDepositCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Transaction result.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupDepositsByCnCContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_deposits_by_contract_and_coin_cb (void *cls,
+                                         PGresult *result,
+                                         unsigned int num_results)
+{
+  struct LookupDepositsByCnCContext *ldcc = cls;
+  struct PostgresClosure *pg = ldcc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
   {
-    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_url),
-      GNUNET_PQ_query_param_string (justification),
-      TALER_PQ_query_param_json (extra),
-      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
+    char *exchange_url;
+    struct TALER_Amount amount_with_fee;
+    struct TALER_Amount deposit_fee;
+    struct TALER_Amount refund_fee;
+    struct TALER_Amount wire_fee;
+    struct GNUNET_HashCode h_wire;
+    struct GNUNET_TIME_Absolute deposit_timestamp;
+    struct GNUNET_TIME_Absolute refund_deadline;
+    struct TALER_ExchangeSignatureP exchange_sig;
+    struct TALER_ExchangePublicKeyP exchange_pub;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                   &amount_with_fee),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
+                                   &deposit_fee),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee",
+                                   &refund_fee),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+                                   &wire_fee),
+      GNUNET_PQ_result_spec_auto_from_type ("h_wire",
+                                            &h_wire),
+      GNUNET_PQ_result_spec_absolute_time ("deposit_timestamp",
+                                           &deposit_timestamp),
+      GNUNET_PQ_result_spec_absolute_time ("refund_deadline",
+                                           &refund_deadline),
+      GNUNET_PQ_result_spec_auto_from_type ("exchange_sig",
+                                            &exchange_sig),
+      GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+                                            &exchange_pub),
+      GNUNET_PQ_result_spec_end
     };
 
-    (void) GNUNET_TIME_round_abs (&now);
-    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_tip_justification",
-                                             params);
-    if (0 > qs)
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
     {
-      postgres_rollback (pg);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-        goto RETRY;
-      return TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR;
+      GNUNET_break (0);
+      ldcc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
     }
+    ldcc->qs = i + 1;
+    ldcc->cb (ldcc->cb_cls,
+              exchange_url,
+              &amount_with_fee,
+              &deposit_fee,
+              &refund_fee,
+              &wire_fee,
+              &h_wire,
+              deposit_timestamp,
+              refund_deadline,
+              &exchange_sig,
+              &exchange_pub);
+    GNUNET_PQ_cleanup_result (rs);
   }
-  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
+ * Lookup information about coin payments by @a h_contract_terms and
+ * @a coin_pub.
  *
- * @param cls closure, typically a connection to the d
- * @param tip_id the unique ID for the tip
- * @param[out] exchange_url set to the URL of the exchange (unless NULL)
- * @param[out] extra extra data to pass to the wallet (unless NULL)
- * @param[out] amount set to the authorized amount (unless NULL)
- * @param[out] amount_left set to the amount left (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
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param h_contract_terms proposal data's hashcode
+ * @param coin_pub public key to use for the search
+ * @param cb function to call with payment data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_lookup_tip_by_id (void *cls,
-                           const struct GNUNET_HashCode *tip_id,
-                           char **exchange_url,
-                           json_t **extra,
-                           struct TALER_Amount *amount,
-                           struct TALER_Amount *amount_left,
-                           struct GNUNET_TIME_Absolute *timestamp)
-{
-  struct PostgresClosure *pg = cls;
-  char *res_exchange_url;
-  json_t *res_extra;
-  struct TALER_Amount res_amount;
-  struct TALER_Amount res_amount_left;
-  struct GNUNET_TIME_Absolute res_timestamp;
+postgres_lookup_deposits_by_contract_and_coin (
+  void *cls,
+  const char *instance_id,
+  const struct GNUNET_HashCode *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  TALER_MERCHANTDB_CoinDepositCallback cb,
+  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (tip_id),
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
     GNUNET_PQ_query_param_end
   };
-  struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_string ("exchange_url",
-                                  &res_exchange_url),
-    GNUNET_PQ_result_spec_absolute_time ("timestamp",
-                                         &res_timestamp),
-    TALER_PQ_result_spec_json ("extra",
-                               &res_extra),
-    TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
-                                 &res_amount),
-    TALER_PQ_RESULT_SPEC_AMOUNT ("left",
-                                 &res_amount_left),
-    GNUNET_PQ_result_spec_end
+  struct LookupDepositsByCnCContext ldcc = {
+    .cb  = cb,
+    .cb_cls = cb_cls,
+    .pg = pg
   };
   enum GNUNET_DB_QueryStatus qs;
 
-  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "find_tip_by_id",
-                                                 params,
-                                                 rs);
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_multi_select (
+    pg->conn,
+    "lookup_deposits_by_contract_and_coin",
+    params,
+    &lookup_deposits_by_contract_and_coin_cb,
+    &ldcc);
   if (0 >= qs)
-  {
-    if (NULL != exchange_url)
-      *exchange_url = NULL;
     return qs;
-  }
-  if (NULL != exchange_url)
-    *exchange_url = strdup (res_exchange_url);
-  if (NULL != amount)
-    *amount = res_amount;
-  if (NULL != amount_left)
-    *amount_left = res_amount_left;
-  if (NULL != timestamp)
-    *timestamp = res_timestamp;
-  if (NULL != extra)
-  {
-    json_incref (res_extra);
-    *extra = res_extra;
-  }
-  GNUNET_PQ_cleanup_result (rs);
-  return qs;
+  return ldcc.qs;
 }
 
 
 /**
- * Pickup a tip over @a amount using pickup id @a pickup_id.
+ * Lookup transfer status.
  *
- * @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)
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param[out] total_amount amount that was debited from our
+ *             aggregate balance at the exchange (in total, sum of
+ *             the wire transfer amount and the @a wire_fee)
+ * @param[out] wire_fee the wire fee the exchange charged
+ * @param[out] execution_time when the transfer was executed by the exchange
+ * @param[out] verified did we confirm the transfer was OK
+ * @return transaction status
  */
-static enum TALER_ErrorCode
-postgres_pickup_tip_TR (void *cls,
-                        const struct TALER_Amount *amount,
-                        const struct GNUNET_HashCode *tip_id,
-                        const struct GNUNET_HashCode *pickup_id,
-                        struct TALER_ReservePrivateKeyP *reserve_priv)
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_transfer (
+  void *cls,
+  const char *exchange_url,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  struct TALER_Amount *total_amount,
+  struct TALER_Amount *wire_fee,
+  struct GNUNET_TIME_Absolute *execution_time,
+  bool *verified)
 {
   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_string (exchange_url),
+    GNUNET_PQ_query_param_auto_from_type (wtid),
     GNUNET_PQ_query_param_end
   };
+  uint8_t verified8;
+  /** Amount we got actually credited, _excludes_ the wire fee */
+  struct TALER_Amount credit_amount;
   struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
-                                          reserve_priv),
-    TALER_PQ_RESULT_SPEC_AMOUNT ("left",
-                                 &left_amount),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount",
+                                 &credit_amount),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+                                 wire_fee),
+    GNUNET_PQ_result_spec_absolute_time ("execution_time",
+                                         execution_time),
+    GNUNET_PQ_result_spec_auto_from_type ("verified",
+                                          &verified8),
     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,
-                      "pickup tip"))
-  {
-    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",
+                                                 "lookup_transfer",
                                                  params,
                                                  rs);
-  if (0 >= qs)
+  if (qs > 0)
   {
-    /* 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;
+    *verified = (0 != verified8);
+    if (0 >
+        TALER_amount_add (total_amount,
+                          &credit_amount,
+                          wire_fee))
+    {
+      GNUNET_break (0);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
   }
+  return qs;
+}
+
+
+/**
+ * Set transfer status to verified.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_set_transfer_status_to_verified (
+  void *cls,
+  const char *exchange_url,
+  const struct TALER_WireTransferIdentifierRawP *wtid)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (wtid),
+    GNUNET_PQ_query_param_string (exchange_url),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (
+    pg->conn,
+    "set_transfer_status_to_verified",
+    params);
+}
+
+
+/**
+ * Closure for #lookup_transfer_summary_cb().
+ */
+struct LookupTransferSummaryContext
+{
+  /**
+   * Function to call for each order that was aggregated.
+   */
+  TALER_MERCHANTDB_TransferSummaryCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Transaction result.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupTransferSummaryContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_transfer_summary_cb (void *cls,
+                            PGresult *result,
+                            unsigned int num_results)
+{
+  struct LookupTransferSummaryContext *ltdc = cls;
+  struct PostgresClosure *pg = ltdc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    char *order_id;
+    struct TALER_Amount deposit_value;
+    struct TALER_Amount deposit_fee;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("order_id",
+                                    &order_id),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value",
+                                   &deposit_value),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee",
+                                   &deposit_fee),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    ltdc->qs = i + 1;
+    ltdc->cb (ltdc->cb_cls,
+              order_id,
+              &deposit_value,
+              &deposit_fee);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Lookup transfer summary.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_transfer_summary (
+  void *cls,
+  const char *exchange_url,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  TALER_MERCHANTDB_TransferSummaryCallback cb,
+  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (exchange_url),
+    GNUNET_PQ_query_param_auto_from_type (wtid),
+    GNUNET_PQ_query_param_end
+  };
+  struct LookupTransferSummaryContext ltdc = {
+    .cb  = cb,
+    .cb_cls = cb_cls,
+    .pg = pg
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_multi_select (
+    pg->conn,
+    "lookup_transfer_summary",
+    params,
+    &lookup_transfer_summary_cb,
+    &ltdc);
+  if (0 >= qs)
+    return qs;
+  return ltdc.qs;
+}
+
+
+/**
+ * Closure for #lookup_transfer_details_cb().
+ */
+struct LookupTransferDetailsContext
+{
+  /**
+   * Function to call for each order that was aggregated.
+   */
+  TALER_MERCHANTDB_TransferDetailsCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Transaction result.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupTransferDetailsContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_transfer_details_cb (void *cls,
+                            PGresult *result,
+                            unsigned int num_results)
+{
+  struct LookupTransferDetailsContext *ltdc = cls;
+  struct PostgresClosure *pg = ltdc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    uint64_t current_offset;
+    struct TALER_TrackTransferDetails ttd;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("offset_in_exchange_list",
+                                    &current_offset),
+      GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+                                            &ttd.h_contract_terms),
+      GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+                                            &ttd.coin_pub),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_value",
+                                   &ttd.coin_value),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_deposit_fee",
+                                   &ttd.coin_fee),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    ltdc->qs = i + 1;
+    ltdc->cb (ltdc->cb_cls,
+              (unsigned int) current_offset,
+              &ttd);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Lookup transfer details.
+ *
+ * @param cls closure
+ * @param exchange_url the exchange that made the transfer
+ * @param wtid wire transfer subject
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_transfer_details (
+  void *cls,
+  const char *exchange_url,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  TALER_MERCHANTDB_TransferDetailsCallback cb,
+  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (exchange_url),
+    GNUNET_PQ_query_param_auto_from_type (wtid),
+    GNUNET_PQ_query_param_end
+  };
+  struct LookupTransferDetailsContext ltdc = {
+    .cb  = cb,
+    .cb_cls = cb_cls,
+    .pg = pg
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_multi_select (
+    pg->conn,
+    "lookup_transfer_details",
+    params,
+    &lookup_transfer_details_cb,
+    &ltdc);
+  if (0 >= qs)
+    return qs;
+  return ltdc.qs;
+}
+
+
+/**
+ * Closure for #lookup_transfers_cb().
+ */
+struct LookupTransfersContext
+{
+  /**
+   * Function to call on results.
+   */
+  TALER_MERCHANTDB_TransferCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Postgres context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Transaction status (set).
+   */
+  enum GNUNET_DB_QueryStatus qs;
+
+  /**
+   * Filter to apply by verification status.
+   */
+  enum TALER_EXCHANGE_YesNoAll verified;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupTransfersContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_transfers_cb (void *cls,
+                     PGresult *result,
+                     unsigned int num_results)
+{
+  struct LookupTransfersContext *ltc = cls;
+  struct PostgresClosure *pg = ltc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_Amount credit_amount;
+    struct TALER_WireTransferIdentifierRawP wtid;
+    char *payto_uri;
+    char *exchange_url;
+    uint64_t transfer_serial_id;
+    struct GNUNET_TIME_Absolute execution_time;
+    enum TALER_EXCHANGE_YesNoAll verified;
+    uint8_t verified8;
+    uint8_t confirmed8;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      TALER_PQ_RESULT_SPEC_AMOUNT ("credit_amount",
+                                   &credit_amount),
+      GNUNET_PQ_result_spec_auto_from_type ("wtid",
+                                            &wtid),
+      GNUNET_PQ_result_spec_string ("payto_uri",
+                                    &payto_uri),
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
+      GNUNET_PQ_result_spec_uint64 ("credit_serial",
+                                    &transfer_serial_id),
+      GNUNET_PQ_result_spec_absolute_time ("execution_time",
+                                           &execution_time),
+      GNUNET_PQ_result_spec_auto_from_type ("verified",
+                                            &verified8),
+      GNUNET_PQ_result_spec_auto_from_type ("confirmed",
+                                            &confirmed8),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    if (0 == verified8)
+      verified = TALER_EXCHANGE_YNA_NO;
+    else
+      verified = TALER_EXCHANGE_YNA_YES;
+    if ( (ltc->verified == TALER_EXCHANGE_YNA_ALL) ||
+         (ltc->verified == verified) )
+    {
+      ltc->qs = i + 1;
+      ltc->cb (ltc->cb_cls,
+               &credit_amount,
+               &wtid,
+               payto_uri,
+               exchange_url,
+               transfer_serial_id,
+               execution_time,
+               TALER_EXCHANGE_YNA_YES == verified,
+               0 != confirmed8);
+    }
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Lookup transfers. Note that filtering by @a verified status is done
+ * outside of SQL, as we already have 8 prepared statements and adding
+ * a filter on verified would further double the number of statements for
+ * a likely rather ineffective filter. So we apply that filter in
+ * #lookup_transfers_cb().
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param payto_uri account that we are interested in transfers to
+ * @param before timestamp for the earliest transfer we care about
+ * @param after timestamp for the last transfer we care about
+ * @param limit number of entries to return, negative for descending in 
execution time,
+ *                positive for ascending in execution time
+ * @param offset transfer_serial number of the transfer we want to offset from
+ * @param verified filter transfers by verification status
+ * @param cb function to call with detailed transfer data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_transfers (void *cls,
+                           const char *instance_id,
+                           const char *payto_uri,
+                           struct GNUNET_TIME_Absolute before,
+                           struct GNUNET_TIME_Absolute after,
+                           int64_t limit,
+                           uint64_t offset,
+                           enum TALER_EXCHANGE_YesNoAll verified,
+                           TALER_MERCHANTDB_TransferCallback cb,
+                           void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  uint64_t plimit = (uint64_t) ((limit < 0) ? -limit : limit);
+  struct GNUNET_PQ_QueryParam params_payto_et[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_absolute_time (&before),
+    GNUNET_PQ_query_param_absolute_time (&after),
+    GNUNET_PQ_query_param_uint64 (&offset),
+    GNUNET_PQ_query_param_uint64 (&plimit),
+    GNUNET_PQ_query_param_string (payto_uri),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_QueryParam params_et[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_absolute_time (&before),
+    GNUNET_PQ_query_param_absolute_time (&after),
+    GNUNET_PQ_query_param_uint64 (&offset),
+    GNUNET_PQ_query_param_uint64 (&plimit),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_QueryParam params_payto[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_uint64 (&offset),
+    GNUNET_PQ_query_param_uint64 (&plimit),
+    GNUNET_PQ_query_param_string (payto_uri),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_QueryParam params_none[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_uint64 (&offset),
+    GNUNET_PQ_query_param_uint64 (&plimit),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_QueryParam *params;
+  struct LookupTransfersContext ltc = {
+    .cb = cb,
+    .cb_cls = cb_cls,
+    .pg = pg,
+    .verified = verified
+  };
+  enum GNUNET_DB_QueryStatus qs;
+  char stmt[128];
+  bool by_time;
+
+  by_time = ( (before.abs_value_us !=
+               GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us) ||
+              (after.abs_value_us  != GNUNET_TIME_UNIT_ZERO_ABS.abs_value_us) 
);
+  check_connection (pg);
+  GNUNET_snprintf (stmt,
+                   sizeof (stmt),
+                   "lookup_transfers%s%s%s",
+                   (by_time) ? "_time" : "",
+                   (NULL != payto_uri) ? "_payto" : "",
+                   (limit > 0) ? "_asc" : "_desc");
+  params = (by_time)
+           ? ( (NULL != payto_uri) ? params_payto_et : params_et)
+           : ( (NULL != payto_uri) ? params_payto : params_none);
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             stmt,
+                                             params,
+                                             &lookup_transfers_cb,
+                                             &ltc);
+  if (0 >= qs)
+    return qs;
+  return ltc.qs;
+}
+
+
+/**
+ * Store information about wire fees charged by an exchange,
+ * including signature (so we have proof).
+ *
+ * @param cls closure
+ * @paramm exchange_pub public key of the exchange
+ * @param h_wire_method hash of wire method
+ * @param wire_fee wire fee charged
+ * @param closing_fee closing fee charged (irrelevant for us,
+ *              but needed to check signature)
+ * @param start_date start of fee being used
+ * @param end_date end of fee being used
+ * @param exchange_sig signature of exchange over fee structure
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_store_wire_fee_by_exchange (
+  void *cls,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct GNUNET_HashCode *h_wire_method,
+  const struct TALER_Amount *wire_fee,
+  const struct TALER_Amount *closing_fee,
+  struct GNUNET_TIME_Absolute start_date,
+  struct GNUNET_TIME_Absolute end_date,
+  const struct
+  TALER_MasterSignatureP *master_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
+    GNUNET_PQ_query_param_auto_from_type (h_wire_method),
+    TALER_PQ_query_param_amount (wire_fee),
+    TALER_PQ_query_param_amount (closing_fee),
+    GNUNET_PQ_query_param_absolute_time (&start_date),
+    GNUNET_PQ_query_param_absolute_time (&end_date),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
+    GNUNET_PQ_query_param_end
+  };
+
+  /* no preflight check here, run in its own transaction by the caller */
+  check_connection (pg);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Storing wire fee for %s starting at %s of %s\n",
+              TALER_B2S (master_pub),
+              GNUNET_STRINGS_absolute_time_to_string (start_date),
+              TALER_amount2s (wire_fee));
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_wire_fee",
+                                             params);
+}
+
+
+/**
+ * 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 now exists.  This has to happen before tips can be
+ * authorized.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance is the reserve tied to
+ * @param reserve_priv which reserve is topped up or created
+ * @param reserve_pub which reserve is topped up or created
+ * @param exchange_url what URL is the exchange reachable at where the reserve 
is located
+ * @param initial_balance how much money will be added to the reserve
+ * @param expiration when does the reserve expire?
+ * @return transaction status, usually
+ *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ */
+static enum TALER_ErrorCode
+postgres_insert_reserve (void *cls,
+                         const char *instance_id,
+                         const struct TALER_ReservePrivateKeyP *reserve_priv,
+                         const struct TALER_ReservePublicKeyP *reserve_pub,
+                         const char *exchange_url,
+                         const struct TALER_Amount *initial_balance,
+                         struct GNUNET_TIME_Absolute expiration)
+{
+  struct PostgresClosure *pg = cls;
+  unsigned int retries;
+  enum GNUNET_DB_QueryStatus qs;
+
+  retries = 0;
+  check_connection (pg);
+RETRY:
+  if (MAX_RETRIES < ++retries)
+    return TALER_EC_TIP_PICKUP_DB_ERROR_SOFT;
+  if (GNUNET_OK !=
+      postgres_start (pg,
+                      "insert reserve"))
+  {
+    GNUNET_break (0);
+    return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+  }
+
+  /* Setup reserve */
+  {
+    struct GNUNET_TIME_Absolute now;
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+      GNUNET_PQ_query_param_absolute_time (&now),
+      GNUNET_PQ_query_param_absolute_time (&expiration),
+      TALER_PQ_query_param_amount (initial_balance),
+      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_reserve",
+                                             params);
+    if (0 > qs)
+    {
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return qs;
+    }
+  }
+  /* Store private key */
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+      GNUNET_PQ_query_param_auto_from_type (reserve_priv),
+      GNUNET_PQ_query_param_string (exchange_url),
+      GNUNET_PQ_query_param_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_reserve_key",
+                                             params);
+    if (0 > qs)
+    {
+      postgres_rollback (pg);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return qs;
+    }
+  }
+  qs = postgres_commit (pg);
+  if (0 <= qs)
+    return TALER_EC_NONE; /* success  */
+  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    goto RETRY;
+  return qs;
+}
+
+
+/**
+ * Confirms @a credit as the amount the exchange claims to have received and
+ * thus really 'activates' the reserve.  This has to happen before tips can
+ * be authorized.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance is the reserve tied to
+ * @param reserve_pub which reserve is topped up or created
+ * @param initial_exchange_balance how much money was be added to the reserve
+ *           according to the exchange
+ * @return transaction status, usually
+ *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_activate_reserve (void *cls,
+                           const char *instance_id,
+                           const struct TALER_ReservePublicKeyP *reserve_pub,
+                           const struct TALER_Amount *initial_exchange_balance)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+    TALER_PQ_query_param_amount (initial_exchange_balance),
+    GNUNET_PQ_query_param_end
+  };
+
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "activate_reserve",
+                                             params);
+}
+
+
+/**
+ * Closure for #lookup_reserves_cb.
+ */
+struct LookupReservesContext
+{
+  /**
+   * Postgres context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Function to call with the results
+   */
+  TALER_MERCHANTDB_ReservesCallback cb;
+
+  /**
+   * Closure for @e cb
+   */
+  void *cb_cls;
+
+  /**
+   * Filter by active reserves.
+   */
+  enum TALER_EXCHANGE_YesNoAll active;
+
+  /**
+   * Filter by failures (missmatch in exchange claimed and
+   * merchant claimed initial amounts).
+   */
+  enum TALER_EXCHANGE_YesNoAll failures;
+
+  /**
+   * Set in case of errors.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param[in,out] cls of type `struct LookupReservesContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_reserves_cb (void *cls,
+                    PGresult *result,
+                    unsigned int num_results)
+{
+  struct LookupReservesContext *lrc = cls;
+  struct PostgresClosure *pg = lrc->pg;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_ReservePublicKeyP reserve_pub;
+    struct GNUNET_TIME_Absolute creation_time;
+    struct GNUNET_TIME_Absolute expiration_time;
+    struct TALER_Amount merchant_initial_balance;
+    struct TALER_Amount exchange_initial_balance;
+    struct TALER_Amount pickup_amount;
+    struct TALER_Amount committed_amount;
+    uint8_t active;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                            &reserve_pub),
+      GNUNET_PQ_result_spec_absolute_time ("creation_time",
+                                           &creation_time),
+      GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                           &expiration_time),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
+                                   &merchant_initial_balance),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
+                                   &exchange_initial_balance),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
+                                   &committed_amount),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
+                                   &pickup_amount),
+      GNUNET_PQ_result_spec_auto_from_type ("active",
+                                            &active),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    switch (lrc->active)
+    {
+    case TALER_EXCHANGE_YNA_YES:
+      if (0 == active)
+        continue;
+    case TALER_EXCHANGE_YNA_NO:
+      if (0 != active)
+        continue;
+    case TALER_EXCHANGE_YNA_ALL:
+      break;
+    }
+    switch (lrc->failures)
+    {
+    case TALER_EXCHANGE_YNA_YES:
+      if (0 ==
+          TALER_amount_cmp (&merchant_initial_balance,
+                            &exchange_initial_balance))
+        continue;
+    case TALER_EXCHANGE_YNA_NO:
+      if (0 !=
+          TALER_amount_cmp (&merchant_initial_balance,
+                            &exchange_initial_balance))
+        continue;
+    case TALER_EXCHANGE_YNA_ALL:
+      break;
+    }
+    lrc->cb (lrc->cb_cls,
+             &reserve_pub,
+             creation_time,
+             expiration_time,
+             &merchant_initial_balance,
+             &exchange_initial_balance,
+             &pickup_amount,
+             &committed_amount,
+             (0 != active));
+  }
+}
+
+
+/**
+ * Lookup reserves.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param created_after filter by reserves created after this date
+ * @param active filter by active reserves
+ * @param failures filter by reserves with a disagreement on the initial 
balance
+ * @param cb function to call with reserve summary data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_reserves (void *cls,
+                          const char *instance_id,
+                          struct GNUNET_TIME_Absolute created_after,
+                          enum TALER_EXCHANGE_YesNoAll active,
+                          enum TALER_EXCHANGE_YesNoAll failures,
+                          TALER_MERCHANTDB_ReservesCallback cb,
+                          void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct LookupReservesContext lrc = {
+    .pg = pg,
+    .active = active,
+    .failures = failures,
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_absolute_time (&created_after),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_reserves",
+                                             params,
+                                             &lookup_reserves_cb,
+                                             &lrc);
+  if (lrc.qs < 0)
+    return lrc.qs;
+  return qs;
+}
+
+
+/**
+ * Closure for #lookup_pending_reserves_cb.
+ */
+struct LookupPendingReservesContext
+{
+  /**
+   * Postgres context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Function to call with the results
+   */
+  TALER_MERCHANTDB_PendingReservesCallback cb;
+
+  /**
+   * Closure for @e cb
+   */
+  void *cb_cls;
+
+  /**
+   * Set in case of errors.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param[in,out] cls of type `struct LookupReservesContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_pending_reserves_cb (void *cls,
+                            PGresult *result,
+                            unsigned int num_results)
+{
+  struct LookupPendingReservesContext *lrc = cls;
+  struct PostgresClosure *pg = lrc->pg;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_ReservePublicKeyP reserve_pub;
+    struct TALER_Amount merchant_initial_balance;
+    char *exchange_url;
+    char *instance_id;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                            &reserve_pub),
+      GNUNET_PQ_result_spec_string ("merchant_id",
+                                    &instance_id),
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
+                                   &merchant_initial_balance),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      lrc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    lrc->cb (lrc->cb_cls,
+             instance_id,
+             exchange_url,
+             &reserve_pub,
+             &merchant_initial_balance);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Lookup reserves pending activation across all instances.
+ *
+ * @param cls closure
+ * @param cb function to call with reserve summary data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_pending_reserves (void *cls,
+                                  TALER_MERCHANTDB_PendingReservesCallback cb,
+                                  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct LookupPendingReservesContext lrc = {
+    .pg = pg,
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_pending_reserves",
+                                             params,
+                                             &lookup_pending_reserves_cb,
+                                             &lrc);
+  if (lrc.qs < 0)
+    return lrc.qs;
+  return qs;
+}
+
+
+/**
+ * Closure for #lookup_reserve_tips_cb().
+ */
+struct LookupTipsContext
+{
+  /**
+   * Postgres context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Array with information about tips generated from this reserve.
+   */
+  struct TALER_MERCHANTDB_TipDetails *tips;
+
+  /**
+   * Length of the @e tips array.
+   */
+  unsigned int tips_length;
+
+  /**
+   * Set in case of errors.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param[in,out] cls of type `struct LookupTipsContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_reserve_tips_cb (void *cls,
+                        PGresult *result,
+                        unsigned int num_results)
+{
+  struct LookupTipsContext *ltc = cls;
+  struct PostgresClosure *pg = ltc->pg;
+
+  GNUNET_array_grow (ltc->tips,
+                     ltc->tips_length,
+                     num_results);
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_MERCHANTDB_TipDetails *td = &ltc->tips[i];
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("justification",
+                                    &td->reason),
+      GNUNET_PQ_result_spec_auto_from_type ("tip_id",
+                                            &td->tip_id),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+                                   &td->total_amount),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ltc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+  }
+}
+
+
+/**
+ * Lookup reserve details.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup payments for
+ * @param reserve_pub public key of the reserve to inspect
+ * @param fetch_tips if true, also return information about tips
+ * @param cb function to call with reserve summary data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_reserve (void *cls,
+                         const char *instance_id,
+                         const struct TALER_ReservePublicKeyP *reserve_pub,
+                         bool fetch_tips,
+                         TALER_MERCHANTDB_ReserveDetailsCallback cb,
+                         void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct LookupTipsContext ltc = {
+    .pg = pg,
+    .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_TIME_Absolute creation_time;
+  struct GNUNET_TIME_Absolute expiration_time;
+  struct TALER_Amount merchant_initial_balance;
+  struct TALER_Amount exchange_initial_balance;
+  struct TALER_Amount pickup_amount;
+  struct TALER_Amount committed_amount;
+  uint8_t active;
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_absolute_time ("creation_time",
+                                         &creation_time),
+    GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                         &expiration_time),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("merchant_initial_balance",
+                                 &merchant_initial_balance),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
+                                 &exchange_initial_balance),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
+                                 &pickup_amount),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
+                                 &committed_amount),
+    GNUNET_PQ_result_spec_auto_from_type ("active",
+                                          &active),
+    GNUNET_PQ_result_spec_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  check_connection (pg);
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "lookup_reserve",
+                                                 params,
+                                                 rs);
+  if (qs < 0)
+    return qs;
+  if (! fetch_tips)
+  {
+    cb (cb_cls,
+        creation_time,
+        expiration_time,
+        &merchant_initial_balance,
+        &exchange_initial_balance,
+        &pickup_amount,
+        &committed_amount,
+        (0 != active),
+        0,
+        NULL);
+    return qs;
+  }
+
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "lookup_reserve_tips",
+                                             params,
+                                             &lookup_reserve_tips_cb,
+                                             &ltc);
+  if (qs < 0)
+    return qs;
+  if (ltc.qs >= 0)
+  {
+    cb (cb_cls,
+        creation_time,
+        expiration_time,
+        &merchant_initial_balance,
+        &exchange_initial_balance,
+        &pickup_amount,
+        &committed_amount,
+        0 != active,
+        ltc.tips_length,
+        ltc.tips);
+  }
+  for (unsigned int i = 0; i<ltc.tips_length; i++)
+    GNUNET_free (ltc.tips[i].reason);
+  GNUNET_array_grow (ltc.tips,
+                     ltc.tips_length,
+                     0);
+  return ltc.qs;
+}
+
+
+/**
+ * Delete a reserve's private key.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance is the reserve tied to
+ * @param reserve_pub which reserve is to be deleted
+ * @return transaction status, usually
+ *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_delete_reserve (void *cls,
+                         const char *instance_id,
+                         const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "delete_reserve",
+                                             params);
+}
+
+
+/**
+ * Purge all of the information about a reserve, including tips.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance is the reserve tied to
+ * @param reserve_pub which reserve is to be purged
+ * @return transaction status, usually
+ *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_purge_reserve (void *cls,
+                        const char *instance_id,
+                        const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "purge_reserve",
+                                             params);
+}
+
+
+/**
+ * Closure for #lookup_reserve_for_tip_cb().
+ */
+struct LookupReserveForTipContext
+{
+  /**
+   * Postgres context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Public key of the reserve we found.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * How much money must be left in the reserve.
+   */
+  struct TALER_Amount required_amount;
+
+  /**
+   * Set to the expiration time of the reserve we found.
+   * #GNUNET_TIME_UNIT_FOREVER_ABS if we found none.
+   */
+  struct GNUNET_TIME_Absolute expiration;
+
+  /**
+   * Error status.
+   */
+  enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * How long must a reserve be at least still valid before we use
+ * it for a tip?
+ */
+#define MIN_EXPIRATION GNUNET_TIME_UNIT_HOURS
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param[in,out] cls of type `struct LookupReserveForTipContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_reserve_for_tip_cb (void *cls,
+                           PGresult *result,
+                           unsigned int num_results)
+{
+  struct LookupReserveForTipContext *lac = cls;
+  struct PostgresClosure *pg = lac->pg;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_ReservePublicKeyP reserve_pub;
+    struct TALER_Amount committed_amount;
+    struct TALER_Amount remaining;
+    struct TALER_Amount initial_balance;
+    struct GNUNET_TIME_Absolute expiration;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                            &reserve_pub),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
+                                   &initial_balance),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
+                                   &committed_amount),
+      GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                           &expiration),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      lac->ec = TALER_EC_TIP_LOOKUP_RESERVE_DB_FAILURE;
+      return;
+    }
+    if (0 >
+        TALER_amount_subtract (&remaining,
+                               &initial_balance,
+                               &committed_amount))
+    {
+      GNUNET_break (0);
+      continue;
+    }
+    if (0 >
+        TALER_amount_cmp (&remaining,
+                          &lac->required_amount))
+      continue; /* insufficient balance */
+    if ( (lac->expiration.abs_value_us !=
+          GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us) &&
+         ( (expiration.abs_value_us > lac->expiration.abs_value_us) &&
+           (GNUNET_TIME_absolute_get_remaining (lac->expiration).rel_value_us >
+            MIN_EXPIRATION.rel_value_us) ) )
+      continue;
+    lac->expiration = expiration;
+    lac->reserve_pub = reserve_pub;
+  }
+}
+
+
+/**
+ * Authorize a tip over @a amount from reserve @a reserve_pub.  Remember
+ * the authorization under @a tip_id for later, together with the
+ * @a justification.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should generate the tip
+ * @param reserve_pub which reserve is debited, NULL to pick one in the DB
+ * @param amount how high is the tip (with fees)
+ * @param justification why was the tip approved
+ * @param next_url where to send the URL post tip pickup
+ * @param[out] tip_id set to the unique ID for the tip
+ * @param[out] expiration set to when the tip expires
+ * @return transaction status,
+ *      #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but 
has expired
+ *      #TALER_EC_TIP_AUTHORIZE_RESERVE_NOT_FOUND if the reserve is not known
+ *      #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has 
insufficient funds left
+ *      #TALER_EC_TIP_AUTHORIZE_DB_START_FAILURE on hard DB errors
+ *      #TALER_EC_TIP_AUTHORIZE_DB_LOOKUP_RESERVE_FAILURE on hard DB errors
+ *      #TALER_EC_TIP_AUTHORIZE_DB_UPDATE_RESERVE_FAILURE on hard DB errors
+ *      #TALER_EC_TIP_AUTHORIZE_DB_RESERVE_INVARIANT_FAILURE on hard DB errors
+ *      #TALER_EC_TIP_AUTHORIZE_DB_START_FAILURE on hard DB errors
+ *      #TALER_EC_TIP_AUTHORIZE_DB_SERIALIZATION_FAILURE on soft DB errors 
(client should retry)
+ *      #TALER_EC_NONE upon success
+ */
+static enum TALER_ErrorCode
+postgres_authorize_tip (void *cls,
+                        const char *instance_id,
+                        const struct TALER_ReservePublicKeyP *reserve_pub,
+                        const struct TALER_Amount *amount,
+                        const char *justification,
+                        const char *next_url,
+                        struct GNUNET_HashCode *tip_id,
+                        struct GNUNET_TIME_Absolute *expiration)
+{
+  struct PostgresClosure *pg = cls;
+  unsigned int retries = 0;
+  enum GNUNET_DB_QueryStatus qs;
+  struct TALER_Amount tips_committed;
+  struct TALER_Amount exchange_initial_balance;
+  const struct TALER_ReservePublicKeyP *reserve_pubp;
+  struct LookupReserveForTipContext lac = {
+    .pg = pg,
+    .required_amount = *amount,
+    .expiration = GNUNET_TIME_UNIT_FOREVER_ABS
+  };
+
+  check_connection (pg);
+RETRY:
+  reserve_pubp = reserve_pub;
+  if (MAX_RETRIES < ++retries)
+  {
+    GNUNET_break (0);
+    return TALER_EC_TIP_AUTHORIZE_DB_SERIALIZATION_FAILURE;
+  }
+  if (GNUNET_OK !=
+      postgres_start (pg,
+                      "enable tip reserve"))
+  {
+    GNUNET_break (0);
+    return TALER_EC_TIP_AUTHORIZE_DB_START_FAILURE;
+  }
+  if (NULL == reserve_pubp)
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      TALER_PQ_query_param_amount (amount),
+      GNUNET_PQ_query_param_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                               "lookup_reserve_for_tip",
+                                               params,
+                                               &lookup_reserve_for_tip_cb,
+                                               &lac);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      postgres_rollback (pg);
+      goto RETRY;
+    }
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_LOOKUP_RESERVE_FAILURE;
+    }
+    if (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us ==
+        lac.expiration.abs_value_us)
+    {
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_RESERVE_NOT_FOUND;
+    }
+    if (0 == GNUNET_TIME_absolute_get_remaining (lac.expiration).rel_value_us)
+    {
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_RESERVE_EXPIRED;
+    }
+    reserve_pubp = &lac.reserve_pub;
+  }
+
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
+      GNUNET_PQ_query_param_end
+    };
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                           expiration),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
+                                   &tips_committed),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("exchange_initial_balance",
+                                   &exchange_initial_balance),
+      GNUNET_PQ_result_spec_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_reserve_status",
+                                                   params,
+                                                   rs);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      postgres_rollback (pg);
+      goto RETRY;
+    }
+    if (qs < 0)
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_LOOKUP_RESERVE_FAILURE;
+    }
+    if (qs == 0)
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_RESERVE_NOT_FOUND;
+    }
+  }
+  {
+    struct TALER_Amount remaining;
+
+    if (0 >=
+        TALER_amount_subtract (&remaining,
+                               &exchange_initial_balance,
+                               &tips_committed))
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_RESERVE_INVARIANT_FAILURE;
+    }
+    if (0 >
+        TALER_amount_cmp (&remaining,
+                          amount))
+    {
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS;
+    }
+  }
+  GNUNET_assert (0 <=
+                 TALER_amount_add (&tips_committed,
+                                   &tips_committed,
+                                   amount));
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
+      TALER_PQ_query_param_amount (&tips_committed),
+      GNUNET_PQ_query_param_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "update_reserve_tips_committed",
+                                             params);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      postgres_rollback (pg);
+      goto RETRY;
+    }
+    if (qs < 0)
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_UPDATE_RESERVE_FAILURE;
+    }
+  }
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              tip_id,
+                              sizeof (*tip_id));
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (reserve_pubp),
+      GNUNET_PQ_query_param_auto_from_type (tip_id),
+      GNUNET_PQ_query_param_string (justification),
+      GNUNET_PQ_query_param_string (next_url),
+      GNUNET_PQ_query_param_absolute_time (expiration),
+      TALER_PQ_query_param_amount (amount),
+      GNUNET_PQ_query_param_end
+    };
+
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_tip",
+                                             params);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      postgres_rollback (pg);
+      goto RETRY;
+    }
+    if (qs < 0)
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return TALER_EC_TIP_AUTHORIZE_DB_UPDATE_RESERVE_FAILURE;
+    }
+  }
+  qs = postgres_commit (pg);
+  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    goto RETRY;
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    postgres_rollback (pg);
+    return TALER_EC_TIP_AUTHORIZE_DB_UPDATE_RESERVE_FAILURE;
+  }
+  return TALER_EC_NONE;
+}
+
+
+/**
+ * Closure for #lookup_signatures_cb().
+ */
+struct LookupSignaturesContext
+{
+  /**
+   * Length of the @e sigs array
+   */
+  unsigned int sigs_length;
+
+  /**
+   * Where to store the signatures.
+   */
+  struct GNUNET_CRYPTO_RsaSignature **sigs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about accounts.
+ *
+ * @param[in,out] cls of type `struct LookupSignaturesContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_signatures_cb (void *cls,
+                      PGresult *result,
+                      unsigned int num_results)
+{
+  struct LookupSignaturesContext *lsc = cls;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    uint32_t offset;
+    struct GNUNET_CRYPTO_RsaSignature *bsig;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint32 ("coin_offset",
+                                    &offset),
+      GNUNET_PQ_result_spec_rsa_signature ("blind_sig",
+                                           &bsig),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      return;
+    }
+    if (offset >= lsc->sigs_length)
+    {
+      GNUNET_break_op (0);
+      GNUNET_PQ_cleanup_result (rs);
+      continue;
+    }
+    /* Must be NULL due to UNIQUE constraint on offset and
+       requirement that client launched us with 'sigs'
+       pre-initialized to NULL. */
+    GNUNET_assert (NULL == lsc->sigs[offset]);
+    lsc->sigs[offset] = bsig;
+  }
+}
+
+
+/**
+ * Lookup pickup details for pickup @a pickup_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param pickup_id which pickup should we lookup details on
+ * @param[out] exchange_url which exchange is the tip withdrawn from
+ * @param[out] reserve_priv private key the tip is withdrawn from (set if 
still available!)
+ * @param sigs_length length of the @a sigs array
+ * @param[out] sigs set to the (blind) signatures we have for this @a 
pickup_id,
+ *              those that are unavailable are left at NULL
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_pickup (void *cls,
+                        const char *instance_id,
+                        const struct GNUNET_HashCode *tip_id,
+                        const struct GNUNET_HashCode *pickup_id,
+                        char **exchange_url,
+                        struct TALER_ReservePrivateKeyP *reserve_priv,
+                        unsigned int sigs_length,
+                        struct GNUNET_CRYPTO_RsaSignature *sigs[])
+{
+  struct PostgresClosure *pg = cls;
+  uint64_t pickup_serial;
+
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (tip_id),
+      GNUNET_PQ_query_param_auto_from_type (pickup_id),
+      GNUNET_PQ_query_param_end
+    };
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    exchange_url),
+      GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
+                                            reserve_priv),
+      GNUNET_PQ_result_spec_uint64 ("pickup_serial",
+                                    &pickup_serial),
+      GNUNET_PQ_result_spec_end
+    };
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_pickup",
+                                                   params,
+                                                   rs);
+    if (qs <= 0)
+      return qs;
+  }
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_uint64 (&pickup_serial),
+      GNUNET_PQ_query_param_end
+    };
+    struct LookupSignaturesContext lsc = {
+      .sigs_length = sigs_length,
+      .sigs = sigs
+    };
+
+    return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                                 "lookup_pickup_signatures",
+                                                 params,
+                                                 &lookup_signatures_cb,
+                                                 &lsc);
+  }
+}
+
+
+/**
+ * Lookup tip details for tip @a tip_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param[out] total_authorized amount how high is the tip (with fees)
+ * @param[out] total_picked_up how much of the tip was so far picked up (with 
fees)
+ * @param[out] expiration set to when the tip expires
+ * @param[out] exchange_url set to the exchange URL where the reserve is
+ * @param[out] reserve_priv set to private key of reserve to be debited
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_tip (void *cls,
+                     const char *instance_id,
+                     const struct GNUNET_HashCode *tip_id,
+                     struct TALER_Amount *total_authorized,
+                     struct TALER_Amount *total_picked_up,
+                     struct GNUNET_TIME_Absolute *expiration,
+                     char **exchange_url,
+                     struct TALER_ReservePrivateKeyP *reserve_priv)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_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",
+                                 total_authorized),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("picked_up",
+                                 total_picked_up),
+    GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                         expiration),
+    GNUNET_PQ_result_spec_string ("exchange_url",
+                                  exchange_url),
+    GNUNET_PQ_result_spec_auto_from_type ("reserve_priv",
+                                          reserve_priv),
+    GNUNET_PQ_result_spec_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_tip",
+                                                   params,
+                                                   rs);
+}
+
+
+/**
+ * Context used for postgres_lookup_tips().
+ */
+struct LookupMerchantTipsContext
+{
+  /**
+   * Postgres context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Function to call with the results.
+   */
+  TALER_MERCHANTDB_TipsCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Internal result.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about tips.
+ *
+ * @param[in,out] cls of type `struct LookupTipsContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_tips_cb (void *cls,
+                PGresult *result,
+                unsigned int num_results)
+{
+  struct LookupMerchantTipsContext *plc = cls;
+  struct PostgresClosure *pg = plc->pg;
 
-  /* Check if pickup_id already exists */
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    uint64_t row_id;
+    struct GNUNET_HashCode tip_id;
+    struct TALER_Amount tip_amount;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("tip_serial",
+                                    &row_id),
+      GNUNET_PQ_result_spec_auto_from_type ("tip_id",
+                                            &tip_id),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+                                   &tip_amount),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      plc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return;
+    }
+    plc->cb (plc->cb_cls,
+             row_id,
+             tip_id,
+             tip_amount);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Lookup tips
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tips for
+ * @param expired should we include expired tips?
+ * @param limit maximum number of results to return, positive for
+ *   ascending row id, negative for descending
+ * @param offset row id to start returning results from
+ * @param cb function to call with tip data
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_tips (void *cls,
+                      const char *instance_id,
+                      enum TALER_EXCHANGE_YesNoAll expired,
+                      int64_t limit,
+                      uint64_t offset,
+                      TALER_MERCHANTDB_TipsCallback cb,
+                      void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct LookupMerchantTipsContext plc = {
+    .pg = pg,
+    .cb = cb,
+    .cb_cls = cb_cls
+  };
+  uint64_t ulimit = (limit > 0) ? limit : -limit;
+  uint8_t bexpired;
+  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (instance_id),
+    GNUNET_PQ_query_param_uint64 (&ulimit),
+    GNUNET_PQ_query_param_uint64 (&offset),
+    GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_auto_from_type (&bexpired),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+  char stmt[128];
+
+  bexpired = (TALER_EXCHANGE_YNA_YES == expired);
+  GNUNET_snprintf (stmt,
+                   sizeof (stmt),
+                   "lookup_tips_%s%s",
+                   (limit > 0) ? "inc" : "dec",
+                   (TALER_EXCHANGE_YNA_ALL == expired) ? "" : "_expired");
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             stmt,
+                                             params,
+                                             &lookup_tips_cb,
+                                             &plc);
+  if (0 != plc.qs)
+    return plc.qs;
+  return qs;
+}
+
+
+/**
+ * Closure for #lookup_pickup_details_cb().
+ */
+struct LookupTipDetailsContext
+{
+  /**
+   * Length of the @e sigs array
+   */
+  unsigned int *pickups_length;
+
+  /**
+   * Where to store the signatures.
+   */
+  struct TALER_MERCHANTDB_PickupDetails **pickups;
+
+  /**
+   * Database handle.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Transaction status.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about pickups.
+ *
+ * @param[in,out] cls of type `struct LookupTipDetailsContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+lookup_pickup_details_cb (void *cls,
+                          PGresult *result,
+                          unsigned int num_results)
+{
+  struct LookupTipDetailsContext *ltdc = cls;
+  struct PostgresClosure *pg = ltdc->pg;
+
+  *ltdc->pickups_length = num_results;
+  *ltdc->pickups = GNUNET_new_array (num_results,
+                                     struct TALER_MERCHANTDB_PickupDetails);
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_MERCHANTDB_PickupDetails *pd = &((*ltdc->pickups)[i]);
+    uint64_t num_planchets = 0;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("pickup_id",
+                                            &pd->pickup_id),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+                                   &pd->requested_amount),
+      GNUNET_PQ_result_spec_uint64 ("num_planchets",
+                                    &num_planchets),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ltdc->qs = GNUNET_DB_STATUS_HARD_ERROR;
+      GNUNET_array_grow (*ltdc->pickups,
+                         *ltdc->pickups_length,
+                         0);
+      return;
+    }
+
+    pd->num_planchets = num_planchets;
+  }
+}
+
+
+/**
+ * Lookup tip details for tip @a tip_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param fpu should we fetch details about individual pickups
+ * @param[out] total_authorized amount how high is the tip (with fees)
+ * @param[out] total_picked_up how much of the tip was so far picked up (with 
fees)
+ * @param[out] justification why was the tip approved
+ * @param[out] expiration set to when the tip expires
+ * @param[out] reserve_pub set to which reserve is debited
+ * @param[out] pickups_length set to the length of @e pickups
+ * @param[out] pickups if @a fpu is true, set to details about the pickup 
operations
+ * @return transaction status,
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_tip_details (void *cls,
+                             const char *instance_id,
+                             const struct GNUNET_HashCode *tip_id,
+                             bool fpu,
+                             struct TALER_Amount *total_authorized,
+                             struct TALER_Amount *total_picked_up,
+                             char **justification,
+                             struct GNUNET_TIME_Absolute *expiration,
+                             struct TALER_ReservePublicKeyP *reserve_pub,
+                             unsigned int *pickups_length,
+                             struct TALER_MERCHANTDB_PickupDetails **pickups)
+{
+  struct PostgresClosure *pg = cls;
+  uint64_t tip_serial;
+  enum GNUNET_DB_QueryStatus qs;
   {
-    struct TALER_Amount existing_amount;
     struct GNUNET_PQ_QueryParam params[] = {
-      GNUNET_PQ_query_param_auto_from_type (pickup_id),
+      GNUNET_PQ_query_param_string (instance_id),
       GNUNET_PQ_query_param_auto_from_type (tip_id),
       GNUNET_PQ_query_param_end
     };
     struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("tip_serial",
+                                    &tip_serial),
       TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
-                                   &existing_amount),
+                                   total_authorized),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("picked_up",
+                                   total_picked_up),
+      GNUNET_PQ_result_spec_string ("justification",
+                                    justification),
+      GNUNET_PQ_result_spec_absolute_time ("expiration",
+                                           expiration),
+      GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                            reserve_pub),
       GNUNET_PQ_result_spec_end
     };
 
+    check_connection (pg);
     qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "lookup_amount_by_pickup",
+                                                   "lookup_tip_details",
                                                    params,
                                                    rs);
+    if (qs <= 0)
+      return qs;
+    if (! fpu)
+    {
+      *pickups_length = 0;
+      *pickups = NULL;
+      return qs;
+    }
+  }
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_uint64 (&tip_serial),
+      GNUNET_PQ_query_param_end
+    };
+
+    struct LookupTipDetailsContext ltdc = {
+      .pickups_length = pickups_length,
+      .pickups = pickups,
+      .pg = pg,
+      .qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+    };
+
+    qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                               "lookup_pickup_details",
+                                               params,
+                                               &lookup_pickup_details_cb,
+                                               &ltdc);
+    if (qs <= 0)
+      return qs;
+    return ltdc.qs;
+  }
+}
+
+
+/**
+ * Insert details about a tip pickup operation.  The @a total_picked_up
+ * UPDATES the total amount under the @a tip_id, while the @a
+ * total_requested is the amount to be associated with this @a pickup_id.
+ * While there is usually only one pickup event that picks up the entire
+ * amount, our schema allows for wallets to pick up the amount incrementally
+ * over multiple pick up operations.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param tip_id the unique ID for the tip
+ * @param total_picked_up how much was picked up overall at this
+ *          point (includes @a total_requested)
+ * @param pickup_id unique ID for the operation
+ * @param total_requested how much is being picked up in this operation
+ * @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_insert_pickup (void *cls,
+                        const char *instance_id,
+                        const struct GNUNET_HashCode *tip_id,
+                        const struct TALER_Amount *total_picked_up,
+                        const struct GNUNET_HashCode *pickup_id,
+                        const struct TALER_Amount *total_requested)
+{
+  struct PostgresClosure *pg = cls;
+  enum GNUNET_DB_QueryStatus qs;
+  unsigned int retries = 0;
+
+  check_connection (pg);
+RETRY:
+  if (MAX_RETRIES < ++retries)
+    return GNUNET_DB_STATUS_SOFT_ERROR;
+  if (GNUNET_OK !=
+      postgres_start (pg,
+                      "insert pickup"))
+  {
+    GNUNET_break (0);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_string (instance_id),
+      GNUNET_PQ_query_param_auto_from_type (tip_id),
+      GNUNET_PQ_query_param_auto_from_type (pickup_id),
+      TALER_PQ_query_param_amount (total_requested),
+      GNUNET_PQ_query_param_end
+    };
+
+    check_connection (pg);
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_pickup",
+                                             params);
     if (0 > qs)
     {
-      /* DB error */
-      memset (reserve_priv,
-              0,
-              sizeof (*reserve_priv));
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
       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;
-      }
-      postgres_commit (pg);
-      return TALER_EC_NONE; /* we are done! */
+      return qs;
     }
   }
 
-  /* Calculate new balance */
   {
-    struct TALER_Amount new_left;
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_auto_from_type (tip_id),
+      TALER_PQ_query_param_amount (total_picked_up),
+      GNUNET_PQ_query_param_end
+    };
 
-    if (0 >
-        TALER_amount_subtract (&new_left,
-                               &left_amount,
-                               amount))
+    check_connection (pg);
+    qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "update_picked_up_tip",
+                                             params);
+    if (0 > qs)
     {
-      /* attempt to take more tips than the tipping amount */
-      GNUNET_break_op (0);
-      memset (reserve_priv,
-              0,
-              sizeof (*reserve_priv));
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
       postgres_rollback (pg);
-      return TALER_EC_TIP_PICKUP_NO_FUNDS;
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        goto RETRY;
+      return qs;
     }
-
-    /* Update DB: update balance */
+  }
+  {
+    uint64_t reserve_serial;
+    struct TALER_Amount reserve_picked_up;
     {
       struct GNUNET_PQ_QueryParam params[] = {
+        GNUNET_PQ_query_param_string (instance_id),
         GNUNET_PQ_query_param_auto_from_type (tip_id),
-        TALER_PQ_query_param_amount (&new_left),
         GNUNET_PQ_query_param_end
       };
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_uint64 ("reserve_serial",
+                                      &reserve_serial),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("tips_picked_up",
+                                     &reserve_picked_up),
+        GNUNET_PQ_result_spec_end
 
-      qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                               "update_tip_balance",
-                                               params);
+      };
+
+      qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                     
"lookup_picked_up_reserve",
+                                                     params,
+                                                     rs);
       if (0 > qs)
       {
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == 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;
+        return qs;
       }
     }
+    if (0 >=
+        TALER_amount_add (&reserve_picked_up,
+                          &reserve_picked_up,
+                          total_requested))
+    {
+      GNUNET_break (0);
+      postgres_rollback (pg);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
 
-    /* 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_uint64 (&reserve_serial),
+        TALER_PQ_query_param_amount (&reserve_picked_up),
         GNUNET_PQ_query_param_end
       };
 
+      check_connection (pg);
       qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                               "insert_pickup_id",
+                                               "update_picked_up_reserve",
                                                params);
       if (0 > qs)
       {
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == 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;
+        return qs;
       }
     }
   }
   qs = postgres_commit (pg);
-  if (0 <= qs)
-    return TALER_EC_NONE; /* success  */
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
   if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     goto RETRY;
-  return TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+  GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+  return qs;
+}
+
+
+/**
+ * Insert blind signature obtained from the exchange during a
+ * tip pickup operation.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param pickup_id unique ID for the operation
+ * @param offset offset of the blind signature for the pickup
+ * @param blind_sig the blind signature
+ * @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_insert_pickup_blind_signature (
+  void *cls,
+  const struct GNUNET_HashCode *pickup_id,
+  uint32_t offset,
+  const struct GNUNET_CRYPTO_RsaSignature *blind_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (pickup_id),
+    GNUNET_PQ_query_param_uint32 (&offset),
+    GNUNET_PQ_query_param_rsa_signature (blind_sig),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_pickup_blind_signature",
+                                             params);
 }
 
 
-/**
- * Initialize Postgres database subsystem.
- *
- * @param cls a configuration instance
- * @return NULL on error, otherwise a `struct TALER_MERCHANTDB_Plugin`
- */
-void *
-libtaler_plugin_merchantdb_postgres_init (void *cls)
-{
-  struct GNUNET_CONFIGURATION_Handle *cfg = cls;
-  struct PostgresClosure *pg;
-  struct TALER_MERCHANTDB_Plugin *plugin;
-  struct GNUNET_PQ_PreparedStatement ps[] = {
+/**
+ * Initialize Postgres database subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_MERCHANTDB_Plugin`
+ */
+void *
+libtaler_plugin_merchantdb_postgres_init (void *cls)
+{
+  const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+  struct PostgresClosure *pg;
+  struct TALER_MERCHANTDB_Plugin *plugin;
+  struct GNUNET_PQ_PreparedStatement ps[] = {
+    GNUNET_PQ_make_prepare ("end_transaction",
+                            "COMMIT",
+                            0),
+    /* for call_with_accounts(), part of postgres_lookup_instances() */
+    GNUNET_PQ_make_prepare ("lookup_instance_private_key",
+                            "SELECT"
+                            " merchant_priv"
+                            " FROM merchant_keys"
+                            " WHERE merchant_serial=$1",
+                            1),
+    /* for find_instances_cb(), part of postgres_lookup_instances() */
+    GNUNET_PQ_make_prepare ("lookup_accounts",
+                            "SELECT"
+                            " h_wire"
+                            ",salt"
+                            ",payto_uri"
+                            ",active"
+                            " FROM merchant_accounts"
+                            " WHERE merchant_serial=$1",
+                            1),
+    /* for postgres_lookup_instances() */
+    GNUNET_PQ_make_prepare ("lookup_instances",
+                            "SELECT"
+                            " merchant_serial"
+                            ",merchant_pub"
+                            ",merchant_id"
+                            ",merchant_name"
+                            ",address"
+                            ",jurisdiction"
+                            ",default_max_deposit_fee_val"
+                            ",default_max_deposit_fee_frac"
+                            ",default_max_wire_fee_val"
+                            ",default_max_wire_fee_frac"
+                            ",default_wire_fee_amortization"
+                            ",default_wire_transfer_delay"
+                            ",default_pay_delay"
+                            " FROM merchant_instances",
+                            0),
+    /* for postgres_insert_instance() */
+    GNUNET_PQ_make_prepare ("insert_instance",
+                            "INSERT INTO merchant_instances"
+                            "(merchant_pub"
+                            ",merchant_id"
+                            ",merchant_name"
+                            ",address"
+                            ",jurisdiction"
+                            ",default_max_deposit_fee_val"
+                            ",default_max_deposit_fee_frac"
+                            ",default_max_wire_fee_val"
+                            ",default_max_wire_fee_frac"
+                            ",default_wire_fee_amortization"
+                            ",default_wire_transfer_delay"
+                            ",default_pay_delay)"
+                            "VALUES"
+                            "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 
$12)",
+                            12),
+    /* for postgres_insert_instance() */
+    GNUNET_PQ_make_prepare ("insert_keys",
+                            "INSERT INTO merchant_keys"
+                            "(merchant_priv"
+                            ",merchant_serial)"
+                            " SELECT $1, merchant_serial"
+                            " FROM merchant_instances"
+                            " WHERE merchant_id=$2",
+                            2),
+    /* for postgres_insert_account() */
+    GNUNET_PQ_make_prepare ("insert_account",
+                            "INSERT INTO merchant_accounts"
+                            "(merchant_serial"
+                            ",h_wire"
+                            ",salt"
+                            ",payto_uri"
+                            ",active)"
+                            " SELECT merchant_serial, $2, $3, $4, $5"
+                            " FROM merchant_instances"
+                            " WHERE merchant_id=$1",
+                            5),
+    /* for postgres_delete_instance_private_key() */
+    GNUNET_PQ_make_prepare ("delete_key",
+                            "DELETE FROM merchant_keys"
+                            " USING merchant_instances"
+                            " WHERE merchant_keys.merchant_serial"
+                            "   = merchant_instances.merchant_serial"
+                            " AND merchant_instances.merchant_id = $1",
+                            1),
+    /* for postgres_purge_instance() */
+    GNUNET_PQ_make_prepare ("purge_keys",
+                            "DELETE FROM merchant_instances"
+                            " WHERE merchant_instances.merchant_id = $1",
+                            1),
+    /* for postgres_update_instance() */
+    GNUNET_PQ_make_prepare ("update_instance",
+                            "UPDATE merchant_instances SET"
+                            " merchant_name=$2"
+                            ",address=$3"
+                            ",jurisdiction=$4"
+                            ",default_max_deposit_fee_val=$5"
+                            ",default_max_deposit_fee_frac=$6"
+                            ",default_max_wire_fee_val=$7"
+                            ",default_max_wire_fee_frac=$8"
+                            ",default_wire_fee_amortization=$9"
+                            ",default_wire_transfer_delay=$10"
+                            ",default_pay_delay=$11"
+                            " WHERE merchant_id = $1",
+                            11),
+    /* for postgres_inactivate_account() */
+    GNUNET_PQ_make_prepare ("inactivate_account",
+                            "UPDATE merchant_accounts SET"
+                            " active=FALSE"
+                            " WHERE h_wire = $1",
+                            1),
+    /* for postgres_lookup_products() */
+    GNUNET_PQ_make_prepare ("lookup_products",
+                            "SELECT"
+                            " product_id"
+                            " FROM merchant_inventory"
+                            " JOIN merchant_instances"
+                            "   USING (merchant_serial)"
+                            " WHERE merchant_instances.merchant_id=$1",
+                            1),
+    /* for postgres_lookup_product() */
+    GNUNET_PQ_make_prepare ("lookup_product",
+                            "SELECT"
+                            " description"
+                            ",description_i18n"
+                            ",unit"
+                            ",price_val"
+                            ",price_frac"
+                            ",taxes"
+                            ",total_stock"
+                            ",total_sold"
+                            ",total_lost"
+                            ",image"
+                            ",merchant_inventory.address"
+                            ",next_restock"
+                            " FROM merchant_inventory"
+                            " JOIN merchant_instances"
+                            "   USING (merchant_serial)"
+                            " WHERE merchant_instances.merchant_id=$1"
+                            "   AND merchant_inventory.product_id=$2",
+                            2),
+    /* for postgres_delete_product() */
+    GNUNET_PQ_make_prepare ("delete_product",
+                            "DELETE"
+                            " FROM merchant_inventory"
+                            " WHERE merchant_inventory.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND merchant_inventory.product_id=$2"
+                            "   AND product_serial NOT IN "
+                            "     (SELECT product_serial FROM 
merchant_order_locks)"
+                            "   AND product_serial NOT IN "
+                            "     (SELECT product_serial FROM 
merchant_inventory_locks)",
+                            2),
+    /* for postgres_insert_product() */
+    GNUNET_PQ_make_prepare ("insert_product",
+                            "INSERT INTO merchant_inventory"
+                            "(merchant_serial"
+                            ",product_id"
+                            ",description"
+                            ",description_i18n"
+                            ",unit"
+                            ",image"
+                            ",taxes"
+                            ",price_val"
+                            ",price_frac"
+                            ",total_stock"
+                            ",address"
+                            ",next_restock)"
+                            " SELECT merchant_serial,"
+                            " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12"
+                            " FROM merchant_instances"
+                            " WHERE merchant_id=$1",
+                            12),
+    /* for postgres_update_product() */
+    GNUNET_PQ_make_prepare ("update_product",
+                            "UPDATE merchant_inventory SET"
+                            " description=$3"
+                            ",description_i18n=$4"
+                            ",unit=$5"
+                            ",image=$6"
+                            ",taxes=$7"
+                            ",price_val=$8"
+                            ",price_frac=$9"
+                            ",total_stock=$10"
+                            ",total_sold=$11"
+                            ",total_lost=$12"
+                            ",address=$13"
+                            ",next_restock=$14"
+                            " WHERE merchant_serial="
+                            "   (SELECT merchant_serial"
+                            "      FROM merchant_instances"
+                            "      WHERE merchant_id=$1)"
+                            "   AND product_id=$2"
+                            "   AND total_stock <= $10"
+                            "   AND total_sold <= $11"
+                            "   AND $10 - $11 >= $12"
+                            "   AND total_lost <= $12",
+                            14),
+
+    /* for postgres_lock_product() */
+    GNUNET_PQ_make_prepare ("lock_product",
+                            "WITH ps AS"
+                            "  (SELECT product_serial"
+                            "   FROM merchant_inventory"
+                            "   WHERE product_id=$2"
+                            "     AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1))"
+                            "INSERT INTO merchant_inventory_locks"
+                            "(product_serial"
+                            ",lock_uuid"
+                            ",total_locked"
+                            ",expiration)"
+                            " SELECT product_serial, $3, $4, $5"
+                            "   FROM merchant_inventory"
+                            "   JOIN ps USING (product_serial)"
+                            "   WHERE "
+                            "     total_stock - total_sold - total_lost - $4 
>= "
+                            "     (SELECT COALESCE(SUM(total_locked), 0)"
+                            "        FROM merchant_inventory_locks"
+                            "        WHERE product_serial=ps.product_serial) + 
"
+                            "     (SELECT COALESCE(SUM(total_locked), 0)"
+                            "        FROM merchant_order_locks"
+                            "        WHERE product_serial=ps.product_serial)",
+                            5),
+    /* for postgres_delete_order() */
+    GNUNET_PQ_make_prepare ("delete_order",
+                            "DELETE"
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND merchant_orders.order_id=$2"
+                            "   AND pay_deadline < $3",
+                            3),
+    /* for postgres_lookup_order() */
+    GNUNET_PQ_make_prepare ("lookup_order",
+                            "SELECT"
+                            " contract_terms"
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND merchant_orders.order_id=$2",
+                            2),
+    /* for postgres_lookup_order_summary() */
+    GNUNET_PQ_make_prepare ("lookup_order_summary",
+                            "(SELECT"
+                            " creation_time"
+                            ",order_serial"
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND merchant_contract_terms.order_id=$2)"
+                            "UNION"
+                            "(SELECT"
+                            " creation_time"
+                            ",order_serial"
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND merchant_orders.order_id=$2)",
+                            2),
+    /* for postgres_lookup_orders() */
+    GNUNET_PQ_make_prepare ("lookup_orders_inc",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_inc_paid",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    CAST($6 as BOOL) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_inc_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            "   AND"
+                            "    BOOL($6) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_inc_refunded_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    BOOL($6) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_inc_paid_refunded_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial > $3"
+                            "   AND"
+                            "    creation_time > $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            "   AND"
+                            "    BOOL($6) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial ASC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec_paid",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    BOOL($6) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($7 as BOOL)" /* otherwise $7 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            "   AND"
+                            "    BOOL($6) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($6 as BOOL)" /* otherwise $6 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec_refunded_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    BOOL($6) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    GNUNET_PQ_make_prepare ("lookup_orders_dec_paid_refunded_wired",
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            " FROM merchant_orders"
+                            " WHERE merchant_orders.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    NOT CAST($5 as BOOL)" /* unclaimed orders are 
never paid */
+                            "   AND"
+                            "    NOT CAST($6 as BOOL)"/* unclaimed orders are 
never refunded */
+                            "   AND"
+                            "    NOT CAST($7 as BOOL)" /* unclaimed orders are 
never wired */
+                            "   AND"
+                            "    order_serial NOT IN"
+                            "     (SELECT order_serial"
+                            "      FROM merchant_contract_terms)" /* only 
select unclaimed orders */
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            "UNION " /* union ensures elements are distinct! */
+                            "(SELECT"
+                            " order_id"
+                            ",order_serial"
+                            ",creation_time"
+                            " FROM merchant_contract_terms"
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND"
+                            "    order_serial < $3"
+                            "   AND"
+                            "    creation_time < $4"
+                            "   AND"
+                            "    BOOL($5) = paid"
+                            "   AND"
+                            "    BOOL($6) = (order_serial IN"
+                            "     (SELECT order_serial "
+                            "      FROM merchant_refunds))"
+                            "   AND"
+                            "    BOOL($7) = wired"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2)"
+                            " ORDER BY order_serial DESC"
+                            " LIMIT $2",
+                            7),
+    /* for postgres_insert_order() */
+    GNUNET_PQ_make_prepare ("insert_order",
+                            "INSERT INTO merchant_orders"
+                            "(merchant_serial"
+                            ",order_id"
+                            ",pay_deadline"
+                            ",creation_time"
+                            ",contract_terms)"
+                            " SELECT merchant_serial,"
+                            " $2, $3, $4, $5"
+                            " FROM merchant_instances"
+                            " WHERE merchant_id=$1",
+                            5),
+    /* for postgres_unlock_inventory() */
+    GNUNET_PQ_make_prepare ("unlock_inventory",
+                            "DELETE"
+                            " FROM merchant_inventory_locks"
+                            " WHERE lock_uuid=$1",
+                            1),
+    /* for postgres_insert_order_lock() */
+    GNUNET_PQ_make_prepare ("insert_order_lock",
+                            "WITH tmp AS"
+                            "  (SELECT "
+                            "      product_serial"
+                            "     ,merchant_serial"
+                            "     ,total_stock"
+                            "     ,total_sold"
+                            "     ,total_lost"
+                            "   FROM merchant_inventory"
+                            "   WHERE product_id=$3"
+                            "     AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1))"
+                            " INSERT INTO merchant_order_locks"
+                            " (product_serial"
+                            " ,total_locked"
+                            " ,order_serial)"
+                            " SELECT tmp.product_serial, $4, order_serial"
+                            "   FROM merchant_orders"
+                            "   JOIN tmp USING(merchant_serial)"
+                            "   WHERE order_id=$2 AND"
+                            "     tmp.total_stock - tmp.total_sold - 
tmp.total_lost - $4 >= "
+                            "     (SELECT COALESCE(SUM(total_locked), 0)"
+                            "        FROM merchant_inventory_locks"
+                            "        WHERE product_serial=tmp.product_serial) 
+ "
+                            "     (SELECT COALESCE(SUM(total_locked), 0)"
+                            "        FROM merchant_order_locks"
+                            "        WHERE product_serial=tmp.product_serial)",
+                            4),
+    /* for postgres_lookup_contract_terms() */
+    GNUNET_PQ_make_prepare ("lookup_contract_terms",
+                            "SELECT"
+                            " contract_terms"
+                            ",order_serial"
+                            " FROM merchant_contract_terms"
+                            " WHERE order_id=$2"
+                            "   AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)",
+                            2),
+    /* for postgres_insert_contract_terms() */
+    GNUNET_PQ_make_prepare ("insert_contract_terms",
+                            "INSERT INTO merchant_contract_terms"
+                            "(order_serial"
+                            ",merchant_serial"
+                            ",order_id"
+                            ",contract_terms"
+                            ",h_contract_terms"
+                            ",creation_time"
+                            ",pay_deadline"
+                            ",refund_deadline"
+                            ",fulfillment_url)"
+                            "SELECT"
+                            " order_serial"
+                            ",merchant_serial"
+                            ",order_id"
+                            ",$3"  /* contract_terms */
+                            ",$4"  /* h_contract_terms */
+                            ",creation_time"
+                            ",$5" /* pay_deadline */
+                            ",$6" /* refund_deadline */
+                            ",$7" /* fulfillment_url */
+                            "FROM merchant_orders"
+                            " WHERE order_id=$2"
+                            "   AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)",
+                            7),
+    /* for postgres_delete_contract_terms() */
+    GNUNET_PQ_make_prepare ("delete_contract_terms",
+                            "DELETE FROM merchant_contract_terms"
+                            " WHERE order_id=$2"
+                            "   AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND ( ( (pay_deadline < $4) AND"
+                            "           (NOT paid) ) OR"
+                            "         (creation_time + $3 > $4) )",
+                            4),
+    /* for postgres_lookup_deposits() */
+    GNUNET_PQ_make_prepare ("lookup_deposits",
+                            "SELECT"
+                            " exchange_url"
+                            ",coin_pub"
+                            ",amount_with_fee_val"
+                            ",amount_with_fee_frac"
+                            ",deposit_fee_val"
+                            ",deposit_fee_frac"
+                            ",refund_fee_val"
+                            ",refund_fee_frac"
+                            ",wire_fee_val"
+                            ",wire_fee_frac"
+                            " FROM merchant_deposits"
+                            " WHERE order_serial="
+                            "     (SELECT order_serial"
+                            "        FROM merchant_contract_terms"
+                            "        WHERE h_contract_terms=$2"
+                            "          AND merchant_serial="
+                            "          (SELECT merchant_serial"
+                            "             FROM merchant_instances"
+                            "            WHERE merchant_id=$1))",
+                            2),
+    /* for postgres_insert_exchange_signkey() */
+    GNUNET_PQ_make_prepare ("insert_exchange_signkey",
+                            "INSERT INTO merchant_exchange_signing_keys"
+                            "(master_pub"
+                            ",exchange_pub"
+                            ",start_date"
+                            ",expire_date"
+                            ",end_date"
+                            ",master_sig)"
+                            "VALUES"
+                            "($1, $2, $3, $4, $5, $6)",
+                            6),
+    /* for postgres_insert_deposit() */
     GNUNET_PQ_make_prepare ("insert_deposit",
+                            "WITH md AS"
+                            "  (SELECT account_serial, merchant_serial"
+                            "   FROM merchant_accounts"
+                            "   WHERE h_wire=$14"
+                            "    AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1))"
+                            ", ed AS"
+                            "  (SELECT signkey_serial"
+                            "   FROM merchant_exchange_signing_keys"
+                            "   WHERE exchange_pub=$16"
+                            "   ORDER BY start_date DESC"
+                            "   LIMIT 1)"
                             "INSERT INTO merchant_deposits"
-                            "(h_contract_terms"
-                            ",merchant_pub"
+                            "(order_serial"
+                            ",deposit_timestamp"
                             ",coin_pub"
                             ",exchange_url"
                             ",amount_with_fee_val"
@@ -3139,287 +7122,415 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
                             ",refund_fee_frac"
                             ",wire_fee_val"
                             ",wire_fee_frac"
-                            ",signkey_pub"
-                            ",exchange_proof) VALUES "
-                            "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 
$12, $13, $14)",
-                            14),
-    GNUNET_PQ_make_prepare ("insert_transfer",
-                            "INSERT INTO merchant_transfers"
-                            "(h_contract_terms"
-                            ",coin_pub"
-                            ",wtid) VALUES "
-                            "($1, $2, $3)",
+                            ",exchange_sig"
+                            ",signkey_serial"
+                            ",account_serial)"
+                            " SELECT "
+                            "   order_serial"
+                            "  ,$3, $4, $5, $6, $7, $8, $9, $10, $11, $12, 
$13, $15"
+                            "  ,ed.signkey_serial"
+                            "  ,md.account_serial"
+                            "  FROM merchant_contract_terms"
+                            "   JOIN md USING (merchant_serial)"
+                            "   FULL OUTER JOIN ed ON TRUE"
+                            "  WHERE h_contract_terms=$2",
+                            16),
+    /* for postgres_lookup_refunds() */
+    GNUNET_PQ_make_prepare ("lookup_refunds",
+                            "SELECT"
+                            " coin_pub"
+                            ",refund_amount_val"
+                            ",refund_amount_frac"
+                            " FROM merchant_refunds"
+                            " WHERE order_serial="
+                            "  (SELECT order_serial"
+                            "     FROM merchant_contract_terms"
+                            "    WHERE h_contract_terms=$2"
+                            "      AND merchant_serial="
+                            "        (SELECT merchant_serial"
+                            "           FROM merchant_instances"
+                            "          WHERE merchant_id=$1))",
+                            2),
+    /* for postgres_mark_contract_paid() */
+    GNUNET_PQ_make_prepare ("mark_contract_paid",
+                            "UPDATE merchant_contract_terms SET"
+                            " paid=TRUE"
+                            ",session_id=$3"
+                            " WHERE h_contract_terms=$2"
+                            "   AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
                             3),
-    GNUNET_PQ_make_prepare ("insert_refund",
+    /* for postgres_mark_contract_paid() */
+    GNUNET_PQ_make_prepare ("mark_inventory_sold",
+                            "UPDATE merchant_inventory SET"
+                            " total_sold=total_sold + order_locks.total_locked"
+                            " FROM (SELECT total_locked,product_serial"
+                            "       FROM merchant_order_locks"
+                            "       WHERE order_serial="
+                            "       (SELECT order_serial"
+                            "          FROM merchant_contract_terms"
+                            "         WHERE h_contract_terms=$2"
+                            "           AND merchant_serial="
+                            "           (SELECT merchant_serial"
+                            "              FROM merchant_instances"
+                            "             WHERE merchant_id=$1))"
+                            "       ) AS order_locks"
+                            " WHERE merchant_inventory.product_serial"
+                            "             =order_locks.product_serial",
+                            2),
+    /* for postgres_mark_contract_paid() */
+    GNUNET_PQ_make_prepare ("delete_completed_order",
+                            "WITH md AS"
+                            "  (SELECT merchant_serial"
+                            "     FROM merchant_instances"
+                            "    WHERE merchant_id=$1) "
+                            "DELETE"
+                            " FROM merchant_orders"
+                            " WHERE order_serial="
+                            "       (SELECT order_serial"
+                            "          FROM merchant_contract_terms"
+                            "          JOIN md USING (merchant_serial)"
+                            "         WHERE h_contract_terms=$2)",
+                            2),
+    /* for postgres_refund_coin() */
+    GNUNET_PQ_make_prepare ("refund_coin",
                             "INSERT INTO merchant_refunds"
-                            "(merchant_pub"
-                            ",h_contract_terms"
+                            "(order_serial"
+                            ",rtransaction_id"
+                            ",refund_timestamp"
                             ",coin_pub"
                             ",reason"
                             ",refund_amount_val"
                             ",refund_amount_frac"
-                            ") VALUES"
-                            "($1, $2, $3, $4, $5, $6)",
-                            6),
-    GNUNET_PQ_make_prepare ("insert_proof",
-                            "INSERT INTO merchant_proofs"
-                            "(exchange_url"
-                            ",wtid"
-                            ",execution_time"
-                            ",signkey_pub"
-                            ",proof) VALUES "
-                            "($1, $2, $3, $4, $5)",
-                            5),
-    GNUNET_PQ_make_prepare ("insert_contract_terms",
-                            "INSERT INTO merchant_contract_terms"
-                            "(order_id"
-                            ",merchant_pub"
-                            ",timestamp"
-                            ",contract_terms"
-                            ",h_contract_terms)"
-                            " VALUES "
-                            "($1, $2, $3, $4, $5)",
-                            5),
-    GNUNET_PQ_make_prepare ("insert_order",
-                            "INSERT INTO merchant_orders"
-                            "(order_id"
-                            ",merchant_pub"
-                            ",timestamp"
-                            ",contract_terms)"
-                            " VALUES "
-                            "($1, $2, $3, $4)",
-                            4),
-    GNUNET_PQ_make_prepare ("insert_session_info",
-                            "INSERT INTO merchant_session_info"
-                            "(session_id"
-                            ",fulfillment_url"
-                            ",order_id"
-                            ",merchant_pub"
-                            ",timestamp)"
-                            " VALUES "
-                            "($1, $2, $3, $4, $5)",
+                            ") "
+                            "SELECT "
+                            " order_serial"
+                            ",0" /* rtransaction_id always 0 for /abort */
+                            ",$3"
+                            ",coin_pub"
+                            ",$5"
+                            ",amount_with_fee_val"
+                            ",amount_with_fee_frac"
+                            " FROM merchant_deposits"
+                            " WHERE coin_pub=$4"
+                            "   AND order_serial="
+                            "  (SELECT order_serial"
+                            "     FROM merchant_contract_terms"
+                            "    WHERE h_contract_terms=$2"
+                            "      AND merchant_serial="
+                            "        (SELECT merchant_serial"
+                            "           FROM merchant_instances"
+                            "          WHERE merchant_id=$1))",
                             5),
-    GNUNET_PQ_make_prepare ("mark_proposal_paid",
-                            "UPDATE merchant_contract_terms SET"
-                            " 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"
-                            ",h_wire_method"
-                            ",wire_fee_val"
-                            ",wire_fee_frac"
-                            ",closing_fee_val"
-                            ",closing_fee_frac"
-                            ",start_date"
-                            ",end_date"
-                            ",exchange_sig)"
-                            " VALUES "
-                            "($1, $2, $3, $4, $5, $6, $7, $8, $9)",
-                            9),
-    GNUNET_PQ_make_prepare ("lookup_wire_fee",
-                            "SELECT"
-                            " wire_fee_val"
-                            ",wire_fee_frac"
-                            ",closing_fee_val"
-                            ",closing_fee_frac"
-                            ",start_date"
-                            ",end_date"
-                            ",exchange_sig"
-                            " FROM exchange_wire_fees"
-                            " WHERE exchange_pub=$1"
-                            "   AND h_wire_method=$2"
-                            "   AND start_date <= $3"
-                            "   AND end_date > $3",
-                            1),
-    GNUNET_PQ_make_prepare ("find_contract_terms_from_hash",
+
+    /* for postgres_lookup_order_status() */
+    GNUNET_PQ_make_prepare ("lookup_order_status",
                             "SELECT"
-                            " contract_terms"
+                            " h_contract_terms"
+                            ",paid"
                             " FROM merchant_contract_terms"
-                            " WHERE h_contract_terms=$1"
-                            "   AND merchant_pub=$2",
+                            " WHERE merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial "
+                            "        FROM merchant_instances"
+                            "        WHERE merchant_id=$1)"
+                            "   AND order_id=$2",
                             2),
-    GNUNET_PQ_make_prepare ("find_paid_contract_terms_from_hash",
+    /* for postgres_lookup_payment_status() */
+    GNUNET_PQ_make_prepare ("lookup_payment_status",
                             "SELECT"
-                            " contract_terms"
+                            " wired"
+                            ",paid"
                             " FROM merchant_contract_terms"
-                            " WHERE h_contract_terms=$1"
-                            "   AND merchant_pub=$2"
-                            "   AND paid=TRUE",
-                            2),
-    GNUNET_PQ_make_prepare ("end_transaction",
-                            "COMMIT",
-                            0),
-
-    GNUNET_PQ_make_prepare ("find_refunds",
-                            "SELECT"
-                            " refund_amount_val"
-                            ",refund_amount_frac"
-                            " FROM merchant_refunds"
-                            " WHERE coin_pub=$1",
+                            " WHERE order_serial=$1",
                             1),
-    GNUNET_PQ_make_prepare ("find_contract_terms_history",
+    /* for postgres_lookup_payment_status() */
+    GNUNET_PQ_make_prepare ("lookup_payment_status_session_id",
                             "SELECT"
-                            " contract_terms"
+                            " wired"
+                            ",paid"
                             " FROM merchant_contract_terms"
-                            " WHERE"
-                            " order_id=$1"
-                            " AND merchant_pub=$2"
-                            " AND paid=TRUE",
+                            " WHERE order_serial=$1"
+                            "   AND session_id=$2",
                             2),
-    GNUNET_PQ_make_prepare ("find_contract_terms",
+    /* for postgres_lookup_deposits_by_order() */
+    GNUNET_PQ_make_prepare ("lookup_deposits_by_order",
                             "SELECT"
-                            " contract_terms"
-                            " FROM merchant_contract_terms"
-                            " WHERE"
-                            " order_id=$1"
-                            " AND merchant_pub=$2",
-                            2),
-    GNUNET_PQ_make_prepare ("find_order",
+                            " deposit_serial"
+                            ",exchange_url"
+                            ",h_wire"
+                            ",amount_with_fee_val"
+                            ",amount_with_fee_frac"
+                            ",deposit_fee_val"
+                            ",deposit_fee_frac"
+                            ",coin_pub"
+                            " FROM merchant_deposits"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            " WHERE order_serial=$1",
+                            1),
+    /* for postgres_lookup_transfer_details_by_order() */
+    GNUNET_PQ_make_prepare ("lookup_transfer_details_by_order",
                             "SELECT"
-                            " contract_terms"
-                            " FROM merchant_orders"
-                            " WHERE"
-                            " order_id=$1"
-                            " AND merchant_pub=$2",
-                            2),
-    GNUNET_PQ_make_prepare ("find_session_info",
+                            " md.deposit_serial"
+                            ",md.exchange_url"
+                            ",mt.wtid"
+                            ",exchange_deposit_value_val"
+                            ",exchange_deposit_value_frac"
+                            ",exchange_deposit_fee_val"
+                            ",exchange_deposit_fee_frac"
+                            ",mt.confirmed AS transfer_confirmed"
+                            " FROM merchant_transfer_to_coin"
+                            " JOIN merchant_deposits AS md USING 
(deposit_serial)"
+                            " JOIN merchant_transfers AS mt USING 
(credit_serial)"
+                            " WHERE deposit_serial IN"
+                            "  (SELECT deposit_serial"
+                            "   FROM merchant_deposits"
+                            "   WHERE order_serial=$1)",
+                            1),
+    /* for postgres_insert_deposit_to_transfer() */
+    GNUNET_PQ_make_prepare ("insert_deposit_to_transfer",
+                            "INSERT INTO merchant_deposit_to_transfer"
+                            "(deposit_serial"
+                            ",coin_contribution_value_val"
+                            ",coin_contribution_value_frac"
+                            ",credit_serial"
+                            ",execution_time"
+                            ",signkey_serial"
+                            ",exchange_sig"
+                            ") SELECT $1, $2, $3, credit_serial, $4, 
signkey_serial, $5"
+                            " FROM merchant_transfers"
+                            " CROSS JOIN merchant_exchange_signing_keys"
+                            " WHERE exchange_pub=$6"
+                            "   AND wtid=$7",
+                            7),
+    /* for postgres_mark_order_wired() */
+    GNUNET_PQ_make_prepare ("mark_order_wired",
+                            "UPDATE merchant_contract_terms SET"
+                            " wired=true"
+                            " WHERE order_serial=$1",
+                            1),
+    /* for process_refund_cb() used in postgres_increase_refund() */
+    GNUNET_PQ_make_prepare ("find_refunds_by_coin",
                             "SELECT"
-                            " order_id"
-                            " FROM merchant_session_info"
-                            " WHERE"
-                            " fulfillment_url=$1"
-                            " AND session_id=$2"
-                            " AND merchant_pub=$3",
+                            " refund_amount_val"
+                            ",refund_amount_frac"
+                            ",rtransaction_id"
+                            " FROM merchant_refunds"
+                            " WHERE coin_pub=$1"
+                            "   AND order_serial=$2",
                             2),
-    GNUNET_PQ_make_prepare ("find_contract_terms_by_date",
-                            "SELECT"
-                            " contract_terms"
-                            ",order_id"
-                            ",row_id"
-                            " FROM merchant_contract_terms"
-                            " WHERE"
-                            " timestamp<$1"
-                            " AND merchant_pub=$2"
-                            " AND paid=TRUE"
-                            " ORDER BY row_id DESC, timestamp DESC"
-                            " LIMIT $3",
-                            3),
-    GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash",
+    /* for process_deposits_for_refund_cb() used in postgres_increase_refund() 
*/
+    GNUNET_PQ_make_prepare ("insert_refund",
+                            "INSERT INTO merchant_refunds"
+                            "(order_serial"
+                            ",rtransaction_id"
+                            ",refund_timestamp"
+                            ",coin_pub"
+                            ",reason"
+                            ",refund_amount_val"
+                            ",refund_amount_frac"
+                            ") VALUES"
+                            "($1, $2, $3, $4, $5, $6, $7)",
+                            7),
+    /* for postgres_increase_refund() */
+    GNUNET_PQ_make_prepare ("find_deposits_for_refund",
                             "SELECT"
                             " coin_pub"
+                            ",order_serial"
+                            ",amount_with_fee_val"
+                            ",amount_with_fee_frac"
+                            " FROM merchant_deposits"
+                            " WHERE order_serial="
+                            "  (SELECT order_serial"
+                            "     FROM merchant_contract_terms"
+                            "    WHERE order_id=$2"
+                            "      AND paid=true"
+                            "      AND merchant_serial="
+                            "        (SELECT merchant_serial"
+                            "           FROM merchant_instances"
+                            "          WHERE merchant_id=$1))",
+                            2),
+    /* for postgres_lookup_refunds_detailed() */
+    GNUNET_PQ_make_prepare ("lookup_refunds_detailed",
+                            "SELECT"
+                            " refund_serial"
+                            ",refund_timestamp"
+                            ",coin_pub"
                             ",merchant_deposits.exchange_url"
                             ",rtransaction_id"
+                            ",reason"
                             ",refund_amount_val"
                             ",refund_amount_frac"
-                            ",merchant_deposits.refund_fee_val"
-                            ",merchant_deposits.refund_fee_frac"
-                            ",reason"
                             " FROM merchant_refunds"
-                            "   JOIN merchant_deposits USING (merchant_pub, 
coin_pub)"
-                            " WHERE merchant_refunds.merchant_pub=$1"
-                            "   AND merchant_refunds.h_contract_terms=$2",
+                            "   JOIN merchant_deposits USING (order_serial, 
coin_pub)"
+                            " WHERE order_serial="
+                            "  (SELECT order_serial"
+                            "     FROM merchant_contract_terms"
+                            "    WHERE h_contract_terms=$2"
+                            "      AND merchant_serial="
+                            "        (SELECT merchant_serial"
+                            "           FROM merchant_instances"
+                            "          WHERE merchant_id=$1))",
                             2),
-    GNUNET_PQ_make_prepare ("get_refund_proof",
-                            "SELECT"
-                            " exchange_pub"
-                            ",exchange_sig"
-                            " FROM merchant_refund_proofs"
-                            " WHERE"
-                            " h_contract_terms=$1"
-                            " AND merchant_pub=$2"
-                            " AND coin_pub=$3"
-                            " AND rtransaction_id=$4",
-                            4),
+    /* for postgres_insert_refund_proof() */
     GNUNET_PQ_make_prepare ("insert_refund_proof",
                             "INSERT INTO merchant_refund_proofs"
-                            "(rtransaction_id"
-                            ",merchant_pub"
-                            ",h_contract_terms"
-                            ",coin_pub"
+                            "(refund_serial"
                             ",exchange_sig"
-                            ",exchange_pub)"
-                            " VALUES "
-                            "($1, $2, $3, $4, $5, $6)",
-                            6),
-    GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_asc",
+                            ",refund_fee_val"
+                            ",refund_fee_frac"
+                            ",signkey_serial)"
+                            "SELECT $1, $2, $3, $4, signkey_serial"
+                            " FROM merchant_exchange_signing_keys"
+                            " WHERE exchange_pub=$5"
+                            "  ORDER BY start_date DESC"
+                            "  LIMIT 1",
+                            5),
+    /* for postgres_lookup_refund_proof() */
+    GNUNET_PQ_make_prepare ("lookup_refund_proof",
                             "SELECT"
-                            " contract_terms"
-                            ",order_id"
-                            ",row_id"
-                            " FROM merchant_contract_terms"
+                            " merchant_exchange_signing_keys.exchange_pub"
+                            ",exchange_sig"
+                            " FROM merchant_refund_proofs"
+                            "  JOIN merchant_exchange_signing_keys"
+                            "    USING (signkey_serial)"
                             " WHERE"
-                            " timestamp>$1"
-                            " AND merchant_pub=$2"
-                            " AND row_id>$3"
-                            " AND paid=TRUE"
-                            " ORDER BY row_id ASC, timestamp ASC"
-                            " LIMIT $4",
-                            4),
-    GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range",
+                            "   refund_serial=$1",
+                            1),
+    /* for postgres_lookup_order_by_fulfillment() */
+    GNUNET_PQ_make_prepare ("lookup_order_by_fulfillment",
                             "SELECT"
-                            " contract_terms"
-                            ",order_id"
-                            ",row_id"
+                            " order_id"
                             " FROM merchant_contract_terms"
-                            " WHERE"
-                            " timestamp>$1"
-                            " AND merchant_pub=$2"
-                            " AND row_id>$3"
-                            " AND paid=TRUE"
-                            " ORDER BY row_id DESC, timestamp DESC"
-                            " LIMIT $4",
-                            4),
-    GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past_asc",
+                            " WHERE fulfillment_url=$2"
+                            "   AND session_id=$3"
+                            "   AND merchant_serial="
+                            "        (SELECT merchant_serial"
+                            "           FROM merchant_instances"
+                            "          WHERE merchant_id=$1)",
+                            3),
+    /* for postgres_insert_transfer() */
+    GNUNET_PQ_make_prepare ("insert_transfer",
+                            "INSERT INTO merchant_transfers"
+                            "(exchange_url"
+                            ",wtid"
+                            ",credit_amount_val"
+                            ",credit_amount_frac"
+                            ",account_serial"
+                            ",confirmed)"
                             "SELECT"
-                            " contract_terms"
-                            ",order_id"
-                            ",row_id"
-                            " FROM merchant_contract_terms"
-                            " WHERE"
-                            " timestamp<$1"
-                            " AND merchant_pub=$2"
-                            " AND row_id<$3"
-                            " AND paid=TRUE"
-                            " ORDER BY row_id ASC, timestamp ASC"
-                            " LIMIT $4",
-                            4),
-    GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_past",
+                            " $1, $2, $3, $4, account_serial, $6"
+                            " FROM merchant_accounts"
+                            " WHERE payto_uri=$5"
+                            "   AND merchant_serial="
+                            "        (SELECT merchant_serial"
+                            "           FROM merchant_instances"
+                            "          WHERE merchant_id=$7)",
+                            7),
+    /* for postgres_lookup_account() */
+    GNUNET_PQ_make_prepare ("lookup_account",
                             "SELECT"
-                            " contract_terms"
-                            ",order_id"
-                            ",row_id"
-                            " FROM merchant_contract_terms"
-                            " WHERE"
-                            " timestamp<$1"
-                            " AND merchant_pub=$2"
-                            " AND row_id<$3"
-                            " AND paid=TRUE"
-                            " ORDER BY row_id DESC, timestamp DESC"
-                            " LIMIT $4",
-                            4),
-    GNUNET_PQ_make_prepare ("find_deposits",
+                            " account_serial"
+                            " FROM merchant_accounts"
+                            " WHERE payto_uri=$2"
+                            "   AND merchant_serial="
+                            "        (SELECT merchant_serial"
+                            "           FROM merchant_instances"
+                            "          WHERE merchant_id=$1)",
+                            2),
+    /* for postgres_insert_transfer_details() */
+    GNUNET_PQ_make_prepare ("lookup_credit_serial",
                             "SELECT"
-                            " coin_pub"
-                            ",exchange_url"
-                            ",amount_with_fee_val"
-                            ",amount_with_fee_frac"
-                            ",deposit_fee_val"
-                            ",deposit_fee_frac"
-                            ",refund_fee_val"
-                            ",refund_fee_frac"
+                            " credit_serial"
+                            " FROM merchant_transfers"
+                            " WHERE exchange_url=$1"
+                            "   AND wtid=$4"
+                            "   AND credit_amount_val=$5"
+                            "   AND credit_amount_frac=$6"
+                            "   AND account_serial="
+                            "        (SELECT account_serial"
+                            "           FROM merchant_accounts"
+                            "          WHERE payto_uri=$2"
+                            "            AND exchange_url=$1"
+                            "            AND merchant_serial="
+                            "            (SELECT merchant_serial"
+                            "               FROM merchant_instances"
+                            "              WHERE merchant_id=$3))",
+                            2),
+    /* for postgres_insert_transfer_details() */
+    GNUNET_PQ_make_prepare ("insert_transfer_signature",
+                            "INSERT INTO merchant_transfer_signatures"
+                            "(credit_serial"
+                            ",signkey_serial"
                             ",wire_fee_val"
                             ",wire_fee_frac"
-                            ",exchange_proof"
+                            ",execution_time"
+                            ",exchange_sig) "
+                            "SELECT $1, signkey_serial, $2, $3, $4, $5"
+                            " FROM merchant_exchange_signing_keys"
+                            " WHERE exchange_pub=$6"
+                            "  ORDER BY start_date DESC"
+                            "  LIMIT 1",
+                            6),
+    /* for postgres_insert_transfer_details() */
+    GNUNET_PQ_make_prepare ("insert_transfer_to_coin_mapping",
+                            "INSERT INTO merchant_transfer_to_coin"
+                            "(deposit_serial"
+                            ",credit_serial"
+                            ",offset_in_exchange_list"
+                            ",exchange_deposit_value_val"
+                            ",exchange_deposit_value_frac"
+                            ",exchange_deposit_fee_val"
+                            ",exchange_deposit_fee_frac) "
+                            "SELECT deposit_serial, $1, $2, $3, $4, $5, $6"
                             " FROM merchant_deposits"
-                            " WHERE h_contract_terms=$1"
-                            " AND merchant_pub=$2",
-                            2),
-    GNUNET_PQ_make_prepare ("find_deposits_by_hash_and_coin",
+                            " JOIN merchant_contract_terms USING 
(order_serial)"
+                            " WHERE coin_pub=$7"
+                            "   AND h_contract_terms=$8",
+                            8),
+    /* for postgres_insert_transfer_details() */
+    GNUNET_PQ_make_prepare ("update_wired_by_coin_pub",
+                            "WITH os AS" /* select orders affected by the coin 
*/
+                            "(SELECT order_serial"
+                            "   FROM merchant_deposits"
+                            "  WHERE coin_pub=$1)"
+                            "UPDATE merchant_contract_terms "
+                            " SET wired=TRUE "
+                            " WHERE order_serial IN "
+                            "  (SELECT order_serial FROM merchant_deposits" /* 
only orders for which NO un-wired coin exists*/
+                            "    WHERE NOT EXISTS "
+                            "    (SELECT order_serial FROM merchant_deposits" 
/* orders for which ANY un-wired coin exists */
+                            "       JOIN os USING (order_serial)" /* filter 
early */
+                            "      WHERE deposit_serial NOT IN"
+                            "      (SELECT deposit_serial " /* all coins 
associated with order that WERE wired */
+                            "         FROM merchant_deposits "
+                            "         JOIN os USING (order_serial)" /* filter 
early */
+                            "         JOIN merchant_deposit_to_transfer USING 
(deposit_serial)"
+                            "         JOIN merchant_transfers USING 
(credit_serial)"
+                            "        WHERE confirmed=TRUE)))",
+                            1),
+    /* for postgres_lookup_wire_fee() */
+    GNUNET_PQ_make_prepare ("lookup_wire_fee",
+                            "SELECT"
+                            " wire_fee_val"
+                            ",wire_fee_frac"
+                            ",closing_fee_val"
+                            ",closing_fee_frac"
+                            ",start_date"
+                            ",end_date"
+                            ",master_sig"
+                            " FROM merchant_exchange_wire_fees"
+                            " WHERE master_pub=$1"
+                            "   AND h_wire_method=$2"
+                            "   AND start_date <= $3"
+                            "   AND end_date > $3",
+                            3),
+    /* for postgres_lookup_deposits_by_contract_and_coin() */
+    GNUNET_PQ_make_prepare ("lookup_deposits_by_contract_and_coin",
                             "SELECT"
-                            " amount_with_fee_val"
+                            " exchange_url"
+                            ",amount_with_fee_val"
                             ",amount_with_fee_frac"
                             ",deposit_fee_val"
                             ",deposit_fee_frac"
@@ -3427,156 +7538,717 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
                             ",refund_fee_frac"
                             ",wire_fee_val"
                             ",wire_fee_frac"
+                            ",h_wire"
+                            ",deposit_timestamp"
+                            ",refund_deadline"
+                            ",exchange_sig"
+                            ",exchange_pub"
+                            " FROM merchant_contract_terms"
+                            "  JOIN merchant_deposits USING (order_serial)"
+                            "  JOIN merchant_exchange_signing_keys USING 
(signkey_serial)"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            " WHERE h_contract_terms=$2"
+                            "   AND coin_pub=$3"
+                            "   AND merchant_contract_terms.merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            3),
+    /* for postgres_lookup_transfer() */
+    GNUNET_PQ_make_prepare ("lookup_transfer",
+                            "SELECT"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wire_fee_val"
+                            ",wire_fee_frac"
+                            ",execution_time"
+                            ",verified"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            " WHERE wtid=$2"
+                            "   AND exchange_url=$1",
+                            2),
+    /* for postgres_set_transfer_status_to_verified() */
+    GNUNET_PQ_make_prepare ("set_transfer_status_to_verified",
+                            "UPDATE merchant_transfers SET"
+                            " verified=TRUE"
+                            " WHERE wtid=$1"
+                            "   AND exchange_url=$2",
+                            2),
+    /* for postgres_lookup_transfer_summary() */
+    GNUNET_PQ_make_prepare ("lookup_transfer_summary",
+                            "SELECT"
+                            " order_id"
+                            ",exchange_deposit_value_val"
+                            ",exchange_deposit_value_frac"
+                            ",exchange_deposit_fee_val"
+                            ",exchange_deposit_fee_frac"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_transfer_to_coin USING 
(credit_serial)"
+                            "  JOIN merchant_deposits USING (deposit_serial)"
+                            "  JOIN merchant_contract_terms USING 
(order_serial)"
+                            " WHERE wtid=$2"
+                            "   AND merchant_transfers.exchange_url=$1",
+                            2),
+    /* for postgres_lookup_transfer_details() */
+    GNUNET_PQ_make_prepare ("lookup_transfer_details",
+                            "SELECT"
+                            " merchant_contract_terms.h_contract_terms"
+                            
",merchant_transfer_to_coin.offset_in_exchange_list"
+                            ",merchant_deposits.coin_pub"
+                            ",exchange_deposit_value_val"
+                            ",exchange_deposit_value_frac"
+                            ",exchange_deposit_fee_val"
+                            ",exchange_deposit_fee_frac"
+                            " FROM merchant_transfer_to_coin"
+                            "  JOIN merchant_deposits USING (deposit_serial)"
+                            "  JOIN merchant_contract_terms USING 
(order_serial)"
+                            "  JOIN merchant_transfers USING (credit_serial)"
+                            " WHERE merchant_transfers.wtid=$2"
+                            "   AND merchant_transfers.exchange_url=$1",
+                            2),
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_asc",
+                            "SELECT"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wtid"
+                            ",merchant_accounts.payto_uri"
                             ",exchange_url"
-                            ",exchange_proof"
-                            " FROM merchant_deposits"
-                            " WHERE h_contract_terms=$1"
-                            " AND merchant_pub=$2"
-                            " AND coin_pub=$3",
+                            ",credit_serial"
+                            ",merchant_transfer_signatures.execution_time"
+                            ",verified"
+                            ",confirmed"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE execution_time < $2"
+                            "   AND execution_time >= $3"
+                            "   AND credit_serial > $4"
+                            "   AND payto_uri = $6"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial ASC"
+                            " LIMIT $5",
+                            6),
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_time_asc",
+                            "SELECT"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wtid"
+                            ",merchant_accounts.payto_uri"
+                            ",exchange_url"
+                            ",credit_serial"
+                            ",merchant_transfer_signatures.execution_time"
+                            ",verified"
+                            ",confirmed"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE execution_time < $2"
+                            "   AND execution_time >= $3"
+                            "   AND credit_serial > $4"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial ASC"
+                            " LIMIT $5",
+                            5),
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_payto_asc",
+                            "SELECT"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wtid"
+                            ",merchant_accounts.payto_uri"
+                            ",exchange_url"
+                            ",credit_serial"
+                            ",CASE WHEN 
(merchant_transfer_signatures.execution_time) IS NULL"
+                            "   THEN 9223372036854775807" /* largest BIGINT 
possible */
+                            "   ELSE 
merchant_transfer_signatures.execution_time"
+                            " END AS execution_time"
+                            ",verified"
+                            ",confirmed"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  LEFT JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE credit_serial > $2"
+                            "   AND payto_uri = $4"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial ASC"
+                            " LIMIT $3",
+                            4),
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_asc",
+                            "SELECT"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wtid"
+                            ",merchant_accounts.payto_uri"
+                            ",exchange_url"
+                            ",credit_serial"
+                            ",CASE WHEN 
(merchant_transfer_signatures.execution_time) IS NULL"
+                            "   THEN 9223372036854775807" /* largest BIGINT 
possible */
+                            "   ELSE 
merchant_transfer_signatures.execution_time"
+                            " END AS execution_time"
+                            ",verified"
+                            ",confirmed"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  LEFT JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE credit_serial > $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial ASC"
+                            " LIMIT $3",
                             3),
-    GNUNET_PQ_make_prepare ("find_transfers_by_hash",
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_time_payto_desc",
                             "SELECT"
-                            " coin_pub"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
                             ",wtid"
-                            ",merchant_proofs.execution_time"
-                            ",merchant_proofs.proof"
+                            ",merchant_accounts.payto_uri"
+                            ",exchange_url"
+                            ",credit_serial"
+                            ",merchant_transfer_signatures.execution_time"
+                            ",verified"
+                            ",confirmed"
                             " FROM merchant_transfers"
-                            "   JOIN merchant_proofs USING (wtid)"
-                            " WHERE h_contract_terms=$1",
-                            1),
-    GNUNET_PQ_make_prepare ("find_deposits_by_wtid",
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE execution_time < $2"
+                            "   AND execution_time >= $3"
+                            "   AND credit_serial < $4"
+                            "   AND payto_uri = $6"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial DESC"
+                            " LIMIT $5",
+                            6),
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_time_desc",
                             "SELECT"
-                            " merchant_transfers.h_contract_terms"
-                            ",merchant_transfers.coin_pub"
-                            ",merchant_deposits.amount_with_fee_val"
-                            ",merchant_deposits.amount_with_fee_frac"
-                            ",merchant_deposits.deposit_fee_val"
-                            ",merchant_deposits.deposit_fee_frac"
-                            ",merchant_deposits.refund_fee_val"
-                            ",merchant_deposits.refund_fee_frac"
-                            ",merchant_deposits.wire_fee_val"
-                            ",merchant_deposits.wire_fee_frac"
-                            ",merchant_deposits.exchange_url"
-                            ",merchant_deposits.exchange_proof"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wtid"
+                            ",merchant_accounts.payto_uri"
+                            ",exchange_url"
+                            ",credit_serial"
+                            ",merchant_transfer_signatures.execution_time"
+                            ",verified"
+                            ",confirmed"
                             " FROM merchant_transfers"
-                            "   JOIN merchant_deposits"
-                            "     USING (h_contract_terms,coin_pub)"
-                            " WHERE wtid=$1",
-                            1),
-    GNUNET_PQ_make_prepare ("find_proof_by_wtid",
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE execution_time < $2"
+                            "   AND execution_time >= $3"
+                            "   AND credit_serial < $4"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial DESC"
+                            " LIMIT $5",
+                            5),
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_payto_desc",
                             "SELECT"
-                            " proof"
-                            " FROM merchant_proofs"
-                            " WHERE wtid=$1"
-                            "  AND exchange_url=$2",
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wtid"
+                            ",merchant_accounts.payto_uri"
+                            ",exchange_url"
+                            ",credit_serial"
+                            ",CASE WHEN 
(merchant_transfer_signatures.execution_time) IS NULL"
+                            "   THEN 9223372036854775807" /* largest BIGINT 
possible */
+                            "   ELSE 
merchant_transfer_signatures.execution_time"
+                            " END AS execution_time"
+                            ",verified"
+                            ",confirmed"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  LEFT JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE credit_serial < $2"
+                            "   AND payto_uri = $4"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial DESC"
+                            " LIMIT $3",
+                            4),
+    /* for postgres_lookup_transfers() */
+    GNUNET_PQ_make_prepare ("lookup_transfers_desc",
+                            "SELECT"
+                            " credit_amount_val"
+                            ",credit_amount_frac"
+                            ",wtid"
+                            ",merchant_accounts.payto_uri"
+                            ",exchange_url"
+                            ",credit_serial"
+                            ",CASE WHEN 
(merchant_transfer_signatures.execution_time) IS NULL"
+                            "   THEN 9223372036854775807" /* largest BIGINT 
possible */
+                            "   ELSE 
merchant_transfer_signatures.execution_time"
+                            " END AS execution_time"
+                            ",verified"
+                            ",confirmed"
+                            " FROM merchant_transfers"
+                            "  JOIN merchant_accounts USING (account_serial)"
+                            "  LEFT JOIN merchant_transfer_signatures USING 
(credit_serial)"
+                            " WHERE credit_serial < $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            " ORDER BY credit_serial DESC"
+                            " LIMIT $3",
+                            3),
+    /* For postgres_store_wire_fee_by_exchange() */
+    GNUNET_PQ_make_prepare ("insert_wire_fee",
+                            "INSERT INTO merchant_exchange_wire_fees"
+                            "(master_pub"
+                            ",h_wire_method"
+                            ",wire_fee_val"
+                            ",wire_fee_frac"
+                            ",closing_fee_val"
+                            ",closing_fee_frac"
+                            ",start_date"
+                            ",end_date"
+                            ",master_sig)"
+                            " VALUES "
+                            "($1, $2, $3, $4, $5, $6, $7, $8, $9)",
+                            9),
+    /* For postgres_insert_reserve() */
+    GNUNET_PQ_make_prepare ("insert_reserve",
+                            "INSERT INTO merchant_tip_reserves"
+                            "(reserve_pub"
+                            ",merchant_serial"
+                            ",creation_time"
+                            ",expiration"
+                            ",merchant_initial_balance_val"
+                            ",merchant_initial_balance_frac"
+                            ")"
+                            "SELECT $2, merchant_serial, $3, $4, $5, $6"
+                            " FROM merchant_instances"
+                            " WHERE merchant_id=$1",
+                            6),
+    /* For postgres_activate_reserve() */
+    GNUNET_PQ_make_prepare ("activate_reserve",
+                            "UPDATE merchant_tip_reserves SET"
+                            " exchange_initial_balance_val=$3"
+                            ",exchange_initial_balance_frac=$4"
+                            " WHERE reserve_pub=$2"
+                            " AND merchant_serial="
+                            "   (SELECT merchant_serial"
+                            "      FROM merchant_instances"
+                            "     WHERE merchant_id=$1)",
+                            4),
+    /* For postgres_insert_reserve() */
+    GNUNET_PQ_make_prepare ("insert_reserve_key",
+                            "INSERT INTO merchant_tip_reserve_keys"
+                            "(reserve_serial"
+                            ",reserve_priv"
+                            ",exchange_url"
+                            ")"
+                            "SELECT reserve_serial, $3, $4"
+                            " FROM merchant_tip_reserves"
+                            " WHERE reserve_pub=$2"
+                            "   AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            4),
+    /* For postgres_lookup_reserves() */
+    GNUNET_PQ_make_prepare ("lookup_reserves",
+                            "SELECT"
+                            " reserve_pub"
+                            ",creation_time"
+                            ",expiration"
+                            ",merchant_initial_balance_val"
+                            ",merchant_initial_balance_frac"
+                            ",exchange_initial_balance_val"
+                            ",exchange_initial_balance_frac"
+                            ",tips_committed_val"
+                            ",tips_committed_frac"
+                            ",tips_picked_up_val"
+                            ",tips_picked_up_frac"
+                            ",reserve_priv IS NOT NULL AS active"
+                            " FROM merchant_tip_reserves"
+                            " FULL OUTER JOIN merchant_tip_reserve_keys USING 
(reserve_serial)"
+                            " WHERE creation_time > $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
                             2),
-    GNUNET_PQ_make_prepare ("lookup_tip_reserve_balance",
+    /* For postgres_lookup_pending_reserves() */
+    GNUNET_PQ_make_prepare ("lookup_pending_reserves",
                             "SELECT"
-                            " expiration"
-                            ",balance_val"
-                            ",balance_frac"
+                            " reserve_pub"
+                            ",merchant_id"
+                            ",exchange_url"
+                            ",merchant_initial_balance_val"
+                            ",merchant_initial_balance_frac"
                             " FROM merchant_tip_reserves"
-                            " WHERE reserve_priv=$1",
-                            1),
-    GNUNET_PQ_make_prepare ("find_tip_authorizations",
+                            " JOIN merchant_instances USING (merchant_serial)"
+                            " JOIN merchant_tip_reserve_keys USING 
(reserve_serial)"
+                            " WHERE exchange_initial_balance_val=0"
+                            "   AND exchange_initial_balance_frac=0",
+                            0),
+    /* For postgres_lookup_reserve() */
+    GNUNET_PQ_make_prepare ("lookup_reserve",
                             "SELECT"
-                            " amount_val"
-                            ",amount_frac"
-                            ",justification"
-                            ",extra"
+                            " creation_time"
+                            ",expiration"
+                            ",merchant_initial_balance_val"
+                            ",merchant_initial_balance_frac"
+                            ",exchange_initial_balance_val"
+                            ",exchange_initial_balance_frac"
+                            ",tips_committed_val"
+                            ",tips_committed_frac"
+                            ",tips_picked_up_val"
+                            ",tips_picked_up_frac"
+                            ",reserve_priv IS NOT NULL AS active"
+                            " FROM merchant_tip_reserves"
+                            " FULL OUTER JOIN merchant_tip_reserve_keys USING 
(reserve_serial)"
+                            " WHERE reserve_pub = $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            2),
+    /* For postgres_lookup_reserve() */
+    GNUNET_PQ_make_prepare ("lookup_reserve_tips",
+                            "SELECT"
+                            " justification"
                             ",tip_id"
+                            ",amount_val"
+                            ",amount_frac"
                             " FROM merchant_tips"
-                            " 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"
-                            " WHERE reserve_priv=$1",
-                            4),
-    GNUNET_PQ_make_prepare ("insert_tip_reserve_balance",
-                            "INSERT INTO merchant_tip_reserves"
-                            "(reserve_priv"
+                            " WHERE reserve_serial ="
+                            "  (SELECT reserve_serial"
+                            "     FROM merchant_tip_reserves"
+                            "      WHERE reserve_pub=$2"
+                            "        AND merchant_serial ="
+                            "       (SELECT merchant_serial"
+                            "          FROM merchant_instances"
+                            "         WHERE merchant_id=$1))",
+                            2),
+    /* for postgres_delete_reserve() */
+    GNUNET_PQ_make_prepare ("delete_reserve",
+                            "DELETE"
+                            " FROM merchant_tip_reserve_keys"
+                            " WHERE reserve_serial="
+                            "   (SELECT reserve_serial"
+                            "      FROM merchant_tip_reserves"
+                            "       WHERE reserve_pub=$2"
+                            "         AND merchant_serial="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1))",
+                            2),
+    /* for postgres_purge_reserve() */
+    GNUNET_PQ_make_prepare ("purge_reserve",
+                            "DELETE"
+                            "   FROM merchant_tip_reserves"
+                            "  WHERE reserve_pub=$2"
+                            "    AND merchant_serial="
+                            "    (SELECT merchant_serial"
+                            "       FROM merchant_instances"
+                            "      WHERE merchant_id=$1)",
+                            2),
+    /* For postgres_authorize_tip() */
+    GNUNET_PQ_make_prepare ("lookup_reserve_for_tip",
+                            "SELECT"
+                            " reserve_pub"
                             ",expiration"
-                            ",balance_val"
-                            ",balance_frac"
-                            ") VALUES "
-                            "($1, $2, $3, $4)",
+                            ",exchange_initial_balance_val"
+                            ",exchange_initial_balance_frac"
+                            ",tips_committed_val"
+                            ",tips_committed_frac"
+                            " FROM merchant_tip_reserves"
+                            " WHERE"
+                            " exchange_initial_balance_val - 
tips_committed_val > $2"
+                            " OR"
+                            " (exchange_initial_balance_val - 
tips_committed_val = $2"
+                            " AND exchange_initial_balance_frac - 
tips_committed_frac >= $3)"
+                            "  AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            3),
+
+    /* For postgres_authorize_tip() */
+    GNUNET_PQ_make_prepare ("lookup_reserve_status",
+                            "SELECT"
+                            " expiration"
+                            ",exchange_initial_balance_val"
+                            ",exchange_initial_balance_frac"
+                            ",tips_committed_val"
+                            ",tips_committed_frac"
+                            " FROM merchant_tip_reserves"
+                            " WHERE reserve_pub = $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            2),
+    /* For postgres_authorize_tip() */
+    GNUNET_PQ_make_prepare ("update_reserve_tips_committed",
+                            "UPDATE merchant_tip_reserves SET"
+                            " tips_committed_val=$3"
+                            ",tips_committed_frac=$4"
+                            " WHERE reserve_pub = $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
                             4),
-    GNUNET_PQ_make_prepare ("insert_tip_justification",
+    /* For postgres_authorize_tip() */
+    GNUNET_PQ_make_prepare ("insert_tip",
                             "INSERT INTO merchant_tips"
-                            "(reserve_priv"
+                            "(reserve_serial"
                             ",tip_id"
-                            ",exchange_url"
                             ",justification"
-                            ",extra"
-                            ",timestamp"
+                            ",next_url"
+                            ",expiration"
                             ",amount_val"
                             ",amount_frac"
-                            ",left_val"
-                            ",left_frac"
-                            ") VALUES "
-                            "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
-                            10),
-    GNUNET_PQ_make_prepare ("lookup_reserve_by_tip_id",
+                            ") "
                             "SELECT"
-                            " reserve_priv"
-                            ",left_val"
-                            ",left_frac"
-                            " FROM merchant_tips"
-                            " WHERE tip_id=$1",
+                            " reserve_serial, $3, $4, $5, $6, $7, $8"
+                            " FROM merchant_tip_reserves"
+                            " WHERE reserve_pub=$2"
+                            "  AND merchant_serial = "
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            8),
+    /* For postgres_lookup_pickup() */
+    GNUNET_PQ_make_prepare ("lookup_pickup",
+                            "SELECT"
+                            " exchange_url"
+                            ",reserve_priv"
+                            ",pickup_serial"
+                            " FROM merchant_tip_pickups"
+                            " JOIN merchant_tips USING (tip_serial)"
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " JOIN merchant_tip_reserve_keys USING 
(reserve_serial)"
+                            " WHERE pickup_id = $3"
+                            "   AND tip_id = $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            2),
+    /* For postgres_lookup_pickup() */
+    GNUNET_PQ_make_prepare ("lookup_pickup_signatures",
+                            "SELECT"
+                            " coin_offset"
+                            ",blind_sig"
+                            " FROM merchant_tip_pickup_signatures"
+                            " WHERE pickup_serial = $1",
                             1),
-    GNUNET_PQ_make_prepare ("lookup_amount_by_pickup",
+
+    /* For postgres_lookup_tip() */
+    GNUNET_PQ_make_prepare ("lookup_tip",
                             "SELECT"
                             " amount_val"
                             ",amount_frac"
-                            " FROM merchant_tip_pickups"
-                            " WHERE pickup_id=$1"
-                            " AND tip_id=$2",
+                            ",picked_up_val"
+                            ",picked_up_frac"
+                            ",merchant_tips.expiration"
+                            ",exchange_url"
+                            ",reserve_priv"
+                            " FROM merchant_tips"
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " JOIN merchant_tip_reserve_keys USING 
(reserve_serial)"
+                            " WHERE tip_id = $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
                             2),
-    GNUNET_PQ_make_prepare ("find_tip_by_id",
+    /* For postgres_lookup_tip() */
+    GNUNET_PQ_make_prepare ("lookup_tips_inc",
                             "SELECT"
-                            " exchange_url"
-                            ",extra"
-                            ",timestamp"
+                            " tip_serial"
+                            ",tip_id"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",CAST($4 as BIGINT)" /* otherwise $4 is unused 
and Postgres unhappy */
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            " FROM merchant_tips"
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " WHERE merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            "   AND"
+                            "    tip_serial > $3"
+                            " ORDER BY tip_serial ASC"
+                            " LIMIT $2",
+                            5),
+    GNUNET_PQ_make_prepare ("lookup_tips_dec",
+                            "SELECT"
+                            " tip_serial"
+                            ",tip_id"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",CAST($4 as BIGINT)" /* otherwise $4 is unused 
and Postgres unhappy */
+                            ",CAST($5 as BOOL)" /* otherwise $5 is unused and 
Postgres unhappy */
+                            " FROM merchant_tips"
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " WHERE merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            "   AND"
+                            "    tip_serial < $3"
+                            " ORDER BY tip_serial DESC"
+                            " LIMIT $2",
+                            5),
+    GNUNET_PQ_make_prepare ("lookup_tips_inc_expired",
+                            "SELECT"
+                            " tip_serial"
+                            ",tip_id"
+                            ",amount_val"
+                            ",amount_frac"
+                            " FROM merchant_tips"
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " WHERE merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            "   AND"
+                            "    tip_serial > $3"
+                            "   AND"
+                            "    CAST($5 as BOOL) = (merchant_tips.expiration 
< $4)"
+                            " ORDER BY tip_serial ASC"
+                            " LIMIT $2",
+                            5),
+    GNUNET_PQ_make_prepare ("lookup_tips_dec_expired",
+                            "SELECT"
+                            " tip_serial"
+                            ",tip_id"
                             ",amount_val"
                             ",amount_frac"
-                            ",left_val"
-                            ",left_frac"
                             " FROM merchant_tips"
-                            " WHERE tip_id=$1",
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " WHERE merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)"
+                            "   AND"
+                            "    tip_serial < $3"
+                            "   AND"
+                            "    CAST($5 as BOOL) = (merchant_tips.expiration 
< $4)"
+                            " ORDER BY tip_serial DESC"
+                            " LIMIT $2",
+                            5),
+    /* for postgres_lookup_tip_details() */
+    GNUNET_PQ_make_prepare ("lookup_tip_details",
+                            "SELECT"
+                            " tip_serial"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",picked_up_val"
+                            ",picked_up_frac"
+                            ",justification"
+                            ",merchant_tips.expiration"
+                            ",reserve_pub"
+                            " FROM merchant_tips"
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " WHERE tip_id = $2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
+                            2),
+    /* for postgres_lookup_tip_details() */
+    GNUNET_PQ_make_prepare ("lookup_pickup_details",
+                            "SELECT"
+                            " pickup_id"
+                            ",amount_val"
+                            ",amount_frac"
+                            ",COUNT(blind_sig) AS num_planchets"
+                            " FROM merchant_tip_pickups"
+                            " JOIN merchant_tip_pickup_signatures USING 
(pickup_serial)"
+                            " WHERE tip_serial = $1"
+                            " GROUP BY pickup_serial",
                             1),
-    GNUNET_PQ_make_prepare ("update_tip_balance",
-                            "UPDATE merchant_tips SET"
-                            " left_val=$2"
-                            ",left_frac=$3"
-                            " WHERE tip_id=$1",
-                            3),
-    GNUNET_PQ_make_prepare ("insert_pickup_id",
+    /* for postgres_insert_pickup() */
+    GNUNET_PQ_make_prepare ("insert_pickup",
                             "INSERT INTO merchant_tip_pickups"
-                            "(tip_id"
+                            "(tip_serial"
                             ",pickup_id"
                             ",amount_val"
                             ",amount_frac"
-                            ") VALUES "
-                            "($1, $2, $3, $4)",
-                            4),
-    GNUNET_PQ_make_prepare ("insert_tip_credit_uuid",
-                            "INSERT INTO merchant_tip_reserve_credits"
-                            "(reserve_priv"
-                            ",credit_uuid"
-                            ",timestamp"
-                            ",amount_val"
-                            ",amount_frac"
-                            ") VALUES "
-                            "($1, $2, $3, $4, $5)",
+                            ") "
+                            "SELECT"
+                            " tip_serial, $3, $4, $5"
+                            " FROM merchant_tips"
+                            " JOIN merchant_tip_reserves USING 
(reserve_serial)"
+                            " WHERE tip_id=$2"
+                            "  AND merchant_serial = "
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
                             5),
-    GNUNET_PQ_make_prepare ("lookup_tip_credit_uuid",
-                            "SELECT 1 "
-                            "FROM merchant_tip_reserve_credits "
-                            "WHERE credit_uuid=$1 AND reserve_priv=$2",
+    /* for postgres_insert_pickup() */
+    GNUNET_PQ_make_prepare ("update_picked_up_tip",
+                            "UPDATE merchant_tips SET"
+                            " picked_up_val=$2"
+                            ",picked_up_frac=$3"
+                            ",was_picked_up = ($2 = amount_val AND $3 = 
amount_frac)"
+                            " WHERE tip_id = $1",
+                            3),
+    /* for postgres_insert_pickup() */
+    GNUNET_PQ_make_prepare ("lookup_picked_up_reserve",
+                            "SELECT"
+                            " reserve_serial"
+                            ",tips_picked_up_val"
+                            ",tips_picked_up_frac"
+                            " FROM merchant_tip_reserves"
+                            " JOIN merchant_tips USING (reserve_serial)"
+                            " WHERE tip_id=$2"
+                            "   AND merchant_serial ="
+                            "     (SELECT merchant_serial"
+                            "        FROM merchant_instances"
+                            "       WHERE merchant_id=$1)",
                             2),
+    /* for postgres_insert_pickup() */
+    GNUNET_PQ_make_prepare ("update_picked_up_reserve",
+                            "UPDATE merchant_tip_reserves SET"
+                            " tips_picked_up_val=$2"
+                            ",tips_picked_up_frac=$3"
+                            " WHERE reserve_serial = $1",
+                            3),
+    /* for postgres_insert_pickup_blind_signature() */
+    GNUNET_PQ_make_prepare ("insert_pickup_blind_signature",
+                            "INSERT INTO merchant_tip_pickup_signatures"
+                            "(pickup_serial"
+                            ",coin_offset"
+                            ",blind_sig"
+                            ") "
+                            "SELECT"
+                            " pickup_serial, $2, $3"
+                            " FROM merchant_tip_pickups"
+                            " WHERE pickup_id=$1",
+                            3),
     GNUNET_PQ_PREPARED_STATEMENT_END
   };
 
@@ -3617,48 +8289,80 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
   plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin);
   plugin->cls = pg;
   plugin->drop_tables = &postgres_drop_tables;
-  plugin->store_deposit = &postgres_store_deposit;
-  plugin->store_coin_to_transfer = &postgres_store_coin_to_transfer;
-  plugin->store_transfer_to_proof = &postgres_store_transfer_to_proof;
-  plugin->store_wire_fee_by_exchange = &postgres_store_wire_fee_by_exchange;
-  plugin->find_payments_by_hash_and_coin =
-    &postgres_find_payments_by_hash_and_coin;
-  plugin->find_payments = &postgres_find_payments;
-  plugin->find_transfers_by_hash = &postgres_find_transfers_by_hash;
-  plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid;
-  plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid;
-  plugin->insert_contract_terms = &postgres_insert_contract_terms;
+  plugin->preflight = &postgres_preflight;
+  plugin->start = &postgres_start;
+  plugin->start_read_committed = &postgres_start_read_committed;
+  plugin->rollback = &postgres_rollback;
+  plugin->commit = &postgres_commit;
+  plugin->lookup_instances = &postgres_lookup_instances;
+  plugin->insert_instance = &postgres_insert_instance;
+  plugin->insert_account = &postgres_insert_account;
+  plugin->delete_instance_private_key = &postgres_delete_instance_private_key;
+  plugin->purge_instance = &postgres_purge_instance;
+  plugin->update_instance = &postgres_update_instance;
+  plugin->inactivate_account = &postgres_inactivate_account;
+  plugin->lookup_products = &postgres_lookup_products;
+  plugin->lookup_product = &postgres_lookup_product;
+  plugin->delete_product = &postgres_delete_product;
+  plugin->insert_product = &postgres_insert_product;
+  plugin->update_product = &postgres_update_product;
+  plugin->lock_product = &postgres_lock_product;
+  plugin->delete_order = &postgres_delete_order;
+  plugin->lookup_order = &postgres_lookup_order;
+  plugin->lookup_order_summary = &postgres_lookup_order_summary;
+  plugin->lookup_orders = &postgres_lookup_orders;
   plugin->insert_order = &postgres_insert_order;
-  plugin->find_order = &postgres_find_order;
-  plugin->find_contract_terms = &postgres_find_contract_terms;
-  plugin->find_contract_terms_history = &postgres_find_contract_terms_history;
-  plugin->find_contract_terms_by_date = &postgres_find_contract_terms_by_date;
-  plugin->get_authorized_tip_amount = &postgres_get_authorized_tip_amount;
-  plugin->find_contract_terms_by_date_and_range =
-    &postgres_find_contract_terms_by_date_and_range;
-  plugin->find_contract_terms_from_hash =
-    &postgres_find_contract_terms_from_hash;
-  plugin->find_paid_contract_terms_from_hash =
-    &postgres_find_paid_contract_terms_from_hash;
-  plugin->get_refunds_from_contract_terms_hash =
-    &postgres_get_refunds_from_contract_terms_hash;
+  plugin->unlock_inventory = &postgres_unlock_inventory;
+  plugin->insert_order_lock = &postgres_insert_order_lock;
+  plugin->lookup_contract_terms = &postgres_lookup_contract_terms;
+  plugin->insert_contract_terms = &postgres_insert_contract_terms;
+  plugin->delete_contract_terms = &postgres_delete_contract_terms;
+  plugin->lookup_deposits = &postgres_lookup_deposits;
+  plugin->insert_exchange_signkey = &postgres_insert_exchange_signkey;
+  plugin->insert_deposit = &postgres_insert_deposit;
+  plugin->lookup_refunds = &postgres_lookup_refunds;
+  plugin->mark_contract_paid = &postgres_mark_contract_paid;
+  plugin->refund_coin = &postgres_refund_coin;
+  plugin->lookup_order_status = &postgres_lookup_order_status;
+  plugin->lookup_payment_status = &postgres_lookup_payment_status;
+  plugin->lookup_deposits_by_order = &postgres_lookup_deposits_by_order;
+  plugin->lookup_transfer_details_by_order =
+    &postgres_lookup_transfer_details_by_order;
+  plugin->insert_deposit_to_transfer = &postgres_insert_deposit_to_transfer;
+  plugin->mark_order_wired = &postgres_mark_order_wired;
+  plugin->increase_refund = &postgres_increase_refund;
+  plugin->lookup_refunds_detailed = &postgres_lookup_refunds_detailed;
+  plugin->insert_refund_proof = &postgres_insert_refund_proof;
+  plugin->lookup_refund_proof = &postgres_lookup_refund_proof;
+  plugin->lookup_order_by_fulfillment = &postgres_lookup_order_by_fulfillment;
+  plugin->insert_transfer = &postgres_insert_transfer;
+  plugin->lookup_account = &postgres_lookup_account;
+  plugin->insert_transfer_details = &postgres_insert_transfer_details;
   plugin->lookup_wire_fee = &postgres_lookup_wire_fee;
-  plugin->increase_refund_for_contract_NT =
-    &postgres_increase_refund_for_contract_NT;
-  plugin->get_refund_proof = &postgres_get_refund_proof;
-  plugin->put_refund_proof = &postgres_put_refund_proof;
-  plugin->mark_proposal_paid = &postgres_mark_proposal_paid;
-  plugin->insert_session_info = &postgres_insert_session_info;
-  plugin->find_session_info = &postgres_find_session_info;
-  plugin->enable_tip_reserve_TR = &postgres_enable_tip_reserve_TR;
-  plugin->authorize_tip_TR = &postgres_authorize_tip_TR;
-  plugin->lookup_tip_by_id = &postgres_lookup_tip_by_id;
-  plugin->pickup_tip_TR = &postgres_pickup_tip_TR;
-  plugin->start = postgres_start;
-  plugin->commit = postgres_commit;
-  plugin->preflight = postgres_preflight;
-  plugin->rollback = postgres_rollback;
-
+  plugin->lookup_deposits_by_contract_and_coin =
+    &postgres_lookup_deposits_by_contract_and_coin;
+  plugin->lookup_transfer = &postgres_lookup_transfer;
+  plugin->set_transfer_status_to_verified =
+    &postgres_set_transfer_status_to_verified;
+  plugin->lookup_transfer_summary = &postgres_lookup_transfer_summary;
+  plugin->lookup_transfer_details = &postgres_lookup_transfer_details;
+  plugin->lookup_transfers = &postgres_lookup_transfers;
+  plugin->store_wire_fee_by_exchange = &postgres_store_wire_fee_by_exchange;
+  plugin->insert_reserve = &postgres_insert_reserve;
+  plugin->activate_reserve = &postgres_activate_reserve;
+  plugin->lookup_reserves = &postgres_lookup_reserves;
+  plugin->lookup_pending_reserves = &postgres_lookup_pending_reserves;
+  plugin->lookup_reserve = &postgres_lookup_reserve;
+  plugin->delete_reserve = &postgres_delete_reserve;
+  plugin->purge_reserve = &postgres_purge_reserve;
+  plugin->authorize_tip = &postgres_authorize_tip;
+  plugin->lookup_pickup = &postgres_lookup_pickup;
+  plugin->lookup_tip = &postgres_lookup_tip;
+  plugin->lookup_tips = &postgres_lookup_tips;
+  plugin->lookup_tip_details = &postgres_lookup_tip_details;
+  plugin->insert_pickup = &postgres_insert_pickup;
+  plugin->insert_pickup_blind_signature =
+    &postgres_insert_pickup_blind_signature;
   return plugin;
 }
 
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 9e3dd22..cf3fa58 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2017 Taler Systems SA
+  (C) 2014-2017, 2020 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
@@ -19,36 +19,12 @@
  * @author Marcello Stanisci
  * @author Christian Grothoff
  */
-
 #include "platform.h"
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
 #include "taler_merchantdb_lib.h"
-#include <jansson.h>
-
-
-#define FAILIF(cond)                            \
-  do {                                          \
-    if (! (cond)) { break;}                       \
-    GNUNET_break (0);                           \
-    goto drop;                                  \
-  } while (0)
-
-#define RND_BLK(ptr)                                                    \
-  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
-
-
-/**
- * Currency we use for the coins.
- */
-#define CURRENCY "EUR"
 
-/**
- * URL we use for the exchange in the database.
- * Note that an exchange does not actually have
- * to run at this address.
- */
-#define EXCHANGE_URL "http://localhost:8888/";
 
 /**
  * Global return value for the test.  Initially -1, set to 0 upon
@@ -61,677 +37,5955 @@ static int result;
  */
 static struct TALER_MERCHANTDB_Plugin *plugin;
 
-/**
- * Hash of the wire transfer address.  Set to some random value.
- */
-static struct GNUNET_HashCode h_wire;
+#define TEST_WITH_FAIL_CLAUSE(test, on_fail) \
+  if (0 != (test)) \
+  { \
+    on_fail \
+  }
 
-/**
- * Transaction ID.
- */
-const char *order_id;
+#define TEST_COND_RET_ON_FAIL(cond, msg) \
+  if (! (cond)) \
+  { \
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
+                msg); \
+    return 1; \
+  }
+
+#define TEST_RET_ON_FAIL(__test) \
+  TEST_WITH_FAIL_CLAUSE (__test, \
+                         return 1; \
+                         )
+
+
+/* ********** Instances ********** */
 
-/**
- * Transaction ID used to test the db query
- * `find_contract_terms_by_date_and_range_future`
- */
-const char *order_id_future;
 
 /**
- * Proposal's hash
+ * Container for instance settings along with keys.
  */
-struct GNUNET_HashCode h_contract_terms;
+struct InstanceData
+{
+  /**
+   * The instance settings.
+   */
+  struct TALER_MERCHANTDB_InstanceSettings instance;
+
+  /**
+   * The public key for the instance.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * The private key for the instance.
+   */
+  struct TALER_MerchantPrivateKeyP merchant_priv;
+};
+
 
 /**
- * Proposal's hash.
+ * Creates data for an instance to use with testing.
+ *
+ * @param instance_id the identifier for this instance.
+ * @param instance the instance data to be filled.
  */
-struct GNUNET_HashCode h_contract_terms_future;
+static void
+make_instance (char *instance_id,
+               struct InstanceData *instance)
+{
+  GNUNET_CRYPTO_eddsa_key_create (&instance->merchant_priv.eddsa_priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (&instance->merchant_priv.eddsa_priv,
+                                      &instance->merchant_pub.eddsa_pub);
+  instance->instance.id = instance_id;
+  instance->instance.name = "Test";
+  instance->instance.address = json_array ();
+  GNUNET_assert (NULL != instance->instance.address);
+  GNUNET_assert (0 == json_array_append_new (instance->instance.address,
+                                             json_string ("123 Example St")));
+  instance->instance.jurisdiction = json_array ();
+  GNUNET_assert (NULL != instance->instance.jurisdiction);
+  GNUNET_assert (0 == json_array_append_new (instance->instance.jurisdiction,
+                                             json_string ("Ohio")));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1200.40",
+                                         &instance->instance.
+                                         default_max_deposit_fee));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1200.40",
+                                         &instance->instance.
+                                         default_max_wire_fee));
+  instance->instance.default_wire_fee_amortization = 1;
+  instance->instance.default_wire_transfer_delay =
+    GNUNET_TIME_relative_get_minute_ ();
+  instance->instance.default_pay_delay = GNUNET_TIME_relative_get_second_ ();
+}
+
 
 /**
- * Time of the transaction.
+ * Frees memory allocated when creating an instance for testing.
+ *
+ * @param instance the instance containing the memory to be freed.
  */
-static struct GNUNET_TIME_Absolute timestamp;
+static void
+free_instance_data (struct InstanceData *instance)
+{
+  json_decref (instance->instance.address);
+  json_decref (instance->instance.jurisdiction);
+}
+
 
 /**
- * Delta aimed to test the "by_date" query on transactions.
+ * Creates an account with test data for an instance.
+ *
+ * @param account the account to initialize.
  */
-static struct GNUNET_TIME_Relative delta;
+static void
+make_account (struct TALER_MERCHANTDB_AccountDetails *account)
+{
+  GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG,
+                                    &account->h_wire);
+  GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG,
+                                    &account->salt);
+  account->payto_uri = "payto://x-taler-bank/bank.demo.taler.net/4";
+  account->active = true;
+}
+
 
 /**
- * Deadline until which refunds are allowed.
+ * Instance settings along with corresponding accounts.
  */
-static struct GNUNET_TIME_Absolute refund_deadline;
+struct InstanceWithAccounts
+{
+  /**
+   * Pointer to the instance settings.
+   */
+  const struct TALER_MERCHANTDB_InstanceSettings *instance;
+
+  /**
+   * Length of the array of accounts.
+   */
+  unsigned int accounts_length;
+
+  /**
+   * Pointer to the array of accounts.
+   */
+  const struct TALER_MERCHANTDB_AccountDetails *accounts;
+
+};
+
 
 /**
- * Total amount, including deposit fee.
+ * Closure for testing instance lookup.
  */
-static struct TALER_Amount amount_with_fee;
+struct TestLookupInstances_Closure
+{
+  /**
+   * Number of instances to compare to.
+   */
+  unsigned int instances_to_cmp_length;
+
+  /**
+   * Pointer to array of instances.
+   */
+  const struct InstanceWithAccounts *instances_to_cmp;
+
+  /**
+   * Pointer to array of number of matches for each instance.
+   */
+  unsigned int *results_matching;
+
+  /**
+   * Total number of results returned.
+   */
+  unsigned int results_length;
+};
+
 
 /**
- * Deposit fee for the coin.
+ * Compares two instances for equality.
+ *
+ * @param a the first instance.
+ * @param b the second instance.
+ * @return 0 on equality, 1 otherwise.
  */
-static struct TALER_Amount deposit_fee;
+static int
+check_instances_equal (const struct TALER_MERCHANTDB_InstanceSettings *a,
+                       const struct TALER_MERCHANTDB_InstanceSettings *b)
+{
+  if ((0 != strcmp (a->id,
+                    b->id)) ||
+      (0 != strcmp (a->name,
+                    b->name)) ||
+      (1 != json_equal (a->address,
+                        b->address)) ||
+      (1 != json_equal (a->jurisdiction,
+                        b->jurisdiction)) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (&a->default_max_deposit_fee,
+                                               &b->default_max_deposit_fee)) ||
+      (0 != TALER_amount_cmp (&a->default_max_deposit_fee,
+                              &b->default_max_deposit_fee)) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (&a->default_max_wire_fee,
+                                               &b->default_max_wire_fee)) ||
+      (0 != TALER_amount_cmp (&a->default_max_wire_fee,
+                              &b->default_max_wire_fee)) ||
+      (a->default_wire_fee_amortization != b->default_wire_fee_amortization) ||
+      (a->default_wire_transfer_delay.rel_value_us !=
+       b->default_wire_transfer_delay.rel_value_us) ||
+      (a->default_pay_delay.rel_value_us != b->default_pay_delay.rel_value_us))
+    return 1;
+  return 0;
+}
+
 
 /**
- * Wire fee of the exchange.
+ * Compares two accounts for equality.
+ *
+ * @param a the first account.
+ * @param b the second account.
+ * @return 0 on equality, 1 otherwise.
  */
-static struct TALER_Amount wire_fee;
+static int
+check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a,
+                      const struct TALER_MERCHANTDB_AccountDetails *b)
+{
+  if ((0 != GNUNET_CRYPTO_hash_cmp (&a->h_wire,
+                                    &b->h_wire)) ||
+      (0 != GNUNET_CRYPTO_hash_cmp (&a->salt,
+                                    &b->salt)) ||
+      (0 != strcmp (a->payto_uri,
+                    b->payto_uri)) ||
+      (a->active != b->active))
+    return 1;
+  return 0;
+}
+
 
 /**
- * Refund fee for the coin.
- */
-static struct TALER_Amount refund_fee;
+ * Called after testing 'lookup_instances'.
+ *
+ * @param cls pointer to 'struct TestLookupInstances_Closure'.
+ * @param merchant_pub public key of the instance
+ * @param merchant_priv private key of the instance, NULL if not available
+ * @param is general instance settings
+ * @param accounts_length length of the @a accounts array
+ * @param accounts list of accounts of the merchant
+*/
+static void
+lookup_instances_cb (void *cls,
+                     const struct TALER_MerchantPublicKeyP *merchant_pub,
+                     const struct TALER_MerchantPrivateKeyP *merchant_priv,
+                     const struct TALER_MERCHANTDB_InstanceSettings *is,
+                     unsigned int accounts_length,
+                     const struct TALER_MERCHANTDB_AccountDetails accounts[])
+{
+  struct TestLookupInstances_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  /* Look through the closure and test each instance for equality */
+  for (unsigned int i = 0; cmp->instances_to_cmp_length > i; ++i)
+  {
+    int accounts_matching[accounts_length];
+    bool accounts_match = true;
+    if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance,
+                                    is))
+      continue;
+    if (accounts_length != cmp->instances_to_cmp[i].accounts_length)
+      continue;
+    /* Count matches between the accounts found and accounts in cls */
+    for (unsigned int j = 0; accounts_length > j; ++j)
+      accounts_matching[j] = 0;
+    for (unsigned int j = 0; accounts_length > j; ++j)
+    {
+      for (unsigned int k = 0; accounts_length > k; ++k)
+      {
+        if (0 == check_accounts_equal (&cmp->instances_to_cmp[i].accounts[j],
+                                       &accounts[k]))
+          accounts_matching[j] += 1;
+      }
+    }
+    /* Each account from the lookup should match with one and only one from 
cls */
+    for (unsigned int j = 0; accounts_length > j; ++j)
+      if (1 != accounts_matching[j])
+        accounts_match = false;
+    if (true == accounts_match)
+      cmp->results_matching[i] += 1;
+  }
+}
+
 
 /**
- * Amount to be refunded.
+ * Tests @e insert_instance.
+ *
+ * @param instance the instance data to insert.
+ * @param expected_result the result that should be returned from the plugin.
+ * @return 0 on success, 1 on failure.
  */
-static struct TALER_Amount refund_amount;
+static int
+test_insert_instance (const struct InstanceData *instance,
+                      enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_instance (plugin->cls,
+                                                  &instance->merchant_pub,
+                                                  &instance->merchant_priv,
+                                                  &instance->instance),
+                         "Insert instance failed\n");
+  return 0;
+}
+
 
 /**
- * Amount to be refunded.  Used to trigger error about
- * subsequent refund amount being lesser than the previous
- * ones.
+ * Tests @e update_instance.
+ *
+ * @param updated_data the instance data to update the row in the database to.
+ * @param expected_result the result that should be returned from the plugin.
+ * @return 0 on success, 1 on failure.
  */
-static struct TALER_Amount little_refund_amount;
+static int
+test_update_instance (const struct InstanceData *updated_data,
+                      enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->update_instance (plugin->cls,
+                                                  &updated_data->instance),
+                         "Update instance failed\n");
+  return 0;
+}
 
 
 /**
- * Amount to be refunded in a call which is subsequent
- * to the good one, expected to succeed.
+ * Tests @e lookup_instances.
+ *
+ * @param active_only whether to lookup all instance, or only active ones.
+ * @param instances_length number of instances to compare to in @e instances.
+ * @param instances a list of instances that will be compared with the results
+ *        found.
+ * @return 0 on success, 1 otherwise.
  */
-static struct TALER_Amount right_second_refund_amount;
+static int
+test_lookup_instances (bool active_only,
+                       unsigned int instances_length,
+                       struct InstanceWithAccounts instances[])
+{
+  unsigned int results_matching[instances_length];
+  struct TestLookupInstances_Closure cmp = {
+    .instances_to_cmp_length = instances_length,
+    .instances_to_cmp = instances,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching, 0, sizeof (unsigned int) * instances_length);
+  if (0 > plugin->lookup_instances (plugin->cls,
+                                    active_only,
+                                    &lookup_instances_cb,
+                                    &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup instances failed\n");
+    return 1;
+  }
+  if (instances_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup instances failed: incorrect number of results\n");
+    return 1;
+  }
+  for (unsigned int i = 0; instances_length > i; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup instances failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
 
 /**
- * Refund amount meant to raise an error because the
- * contract's coins aren't enough to pay it back
+ * Tests removing the private key of the given instance from the database.
+ *
+ * @param instance the instance whose private key to delete.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 on success, 1 otherwise.
  */
-static struct TALER_Amount too_big_refund_amount;
+static int
+test_delete_instance_private_key (const struct InstanceData *instance,
+                                  enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->delete_instance_private_key (plugin->cls,
+                                                              
instance->instance
+                                                              .id),
+                         "Delete instance private key failed\n");
+  return 0;
+}
+
 
 /**
- * Public key of the coin.  Set to some random value.
+ * Tests inserting an account for a merchant instance.
+ *
+ * @param instance the instance to associate the account with.
+ * @param account the account to insert.
+ * @param expected_result the result expected from the db.
+ * @return 0 on success, 1 otherwise.
  */
-static struct TALER_CoinSpendPublicKeyP coin_pub;
+static int
+test_insert_account (const struct InstanceData *instance,
+                     const struct TALER_MERCHANTDB_AccountDetails *account,
+                     enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_account (plugin->cls,
+                                                 instance->instance.id,
+                                                 account),
+                         "Insert account failed\n");
+  return 0;
+}
+
 
 /**
- * Public key of the exchange.  Set to some random value.
+ * Tests deactivating an account.
+ *
+ * @param account the account to inactivate.
+ * @param expected_result the result expected from the plugin.
+ * @return 0 on success, 1 otherwise.
  */
-static struct TALER_ExchangePublicKeyP signkey_pub;
+static int
+test_inactivate_account (const struct TALER_MERCHANTDB_AccountDetails *account,
+                         enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->inactivate_account (plugin->cls,
+                                                     &account->h_wire),
+                         "Inactivate account failed\n");
+  return 0;
+}
+
 
 /**
- * Public Key of the merchant. Set to some random value.
- * Used as merchant instances now do store their keys.
+ * Closure for instance tests
  */
-static struct TALER_MerchantPublicKeyP merchant_pub;
+struct TestInstances_Closure
+{
+  /**
+   * The list of instances that we use for testing instances.
+   */
+  struct InstanceData instances[2];
+
+  /**
+   * The list of accounts to use with the instances.
+   */
+  struct TALER_MERCHANTDB_AccountDetails accounts[2];
+
+};
+
 
 /**
- * Wire transfer identifier.  Set to some random value.
+ * Sets up the data structures used in the instance tests
+ *
+ * @cls the closure to initialize with test data.
  */
-static struct TALER_WireTransferIdentifierRawP wtid;
+static void
+pre_test_instances (struct TestInstances_Closure *cls)
+{
+  /* Instance */
+  make_instance ("test_instances_inst0",
+                 &cls->instances[0]);
+  make_instance ("test_instances_inst1",
+                 &cls->instances[1]);
+
+  /* Accounts */
+  make_account (&cls->accounts[0]);
+  make_account (&cls->accounts[1]);
+}
+
 
 /**
- * "Proof" of deposit from the exchange. Set to some valid JSON.
+ * Handles all teardown after testing
+ *
+ * @cls the closure to free data from
  */
-static json_t *deposit_proof;
+static void
+post_test_instances (struct TestInstances_Closure *cls)
+{
+  free_instance_data (&cls->instances[0]);
+  free_instance_data (&cls->instances[1]);
+}
+
 
 /**
- * "Proof" of wire transfer from the exchange. Set to some valid JSON.
+ * Function that tests instances.
+ *
+ * @param cls closure with config
+ * @return 0 on success, 1 if failure.
  */
-static json_t *transfer_proof;
+static int
+run_test_instances (struct TestInstances_Closure *cls)
+{
+  struct InstanceWithAccounts instances[2] = {
+    {
+      .accounts_length = 0,
+      .accounts = cls->accounts,
+      .instance = &cls->instances[0].instance
+    },
+    {
+      .accounts_length = 0,
+      .accounts = cls->accounts,
+      .instance = &cls->instances[1].instance
+    }
+  };
+  uint64_t account_serial;
+
+  /* Test inserting an instance */
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test double insertion fails */
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
+                                          
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test lookup instances- is our new instance there? */
+  TEST_RET_ON_FAIL (test_lookup_instances (false,
+                                           1,
+                                           instances));
+  /* Test update instance */
+  cls->instances[0].instance.name = "Test - updated";
+  json_array_append_new (cls->instances[0].instance.address,
+                         json_pack ("{s:s, s:I}",
+                                    "this", "is",
+                                    "more data", 47));
+  json_array_append_new (cls->instances[0].instance.jurisdiction,
+                         json_pack ("{s:s}",
+                                    "vegetables", "bad"));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.04",
+                                         &cls->instances[0].instance.
+                                         default_max_deposit_fee));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1.23",
+                                         &cls->instances[0].instance.
+                                         default_max_wire_fee));
+  cls->instances[0].instance.default_wire_fee_amortization = 2;
+  cls->instances[0].instance.default_wire_transfer_delay =
+    GNUNET_TIME_UNIT_HOURS;
+  cls->instances[0].instance.default_pay_delay = GNUNET_TIME_UNIT_MINUTES;
+
+  TEST_RET_ON_FAIL (test_update_instance (&cls->instances[0],
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_instances (false,
+                                           1,
+                                           instances));
+  TEST_RET_ON_FAIL (test_update_instance (&cls->instances[1],
+                                          
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test account creation */
+  TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
+                                         &cls->accounts[0],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test double account insertion fails */
+  TEST_RET_ON_FAIL (test_insert_account (&cls->instances[1],
+                                         &cls->accounts[1],
+                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
+                                         &cls->accounts[0],
+                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  instances[0].accounts_length = 1;
+  TEST_RET_ON_FAIL (test_lookup_instances (false,
+                                           1,
+                                           instances));
+  /* Test inactivate account */
+  TEST_RET_ON_FAIL (test_inactivate_account (&cls->accounts[0],
+                                             
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_inactivate_account (&cls->accounts[1],
+                                             
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  cls->accounts[0].active = false;
+  TEST_RET_ON_FAIL (test_lookup_instances (false,
+                                           1,
+                                           instances));
+  /* Test lookup account */
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->lookup_account (plugin->cls,
+                              cls->instances[0].instance.id,
+                              cls->accounts[0].payto_uri,
+                              &account_serial))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup account failed\n");
+    return 1;
+  }
+  if (1 != account_serial)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup account failed: incorrect serial number found\n");
+    return 1;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+      plugin->lookup_account (plugin->cls,
+                              cls->instances[0].instance.id,
+                              "payto://other-uri",
+                              &account_serial))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup account failed: account found where there is none\n");
+    return 1;
+  }
+  /* Test instance private key deletion */
+  TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0],
+                                                      
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[1],
+                                                      
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  TEST_RET_ON_FAIL (test_lookup_instances (true,
+                                           0,
+                                           NULL));
+  TEST_RET_ON_FAIL (test_lookup_instances (false,
+                                           1,
+                                           instances));
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[1],
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_instances (false,
+                                           2,
+                                           instances));
+  TEST_RET_ON_FAIL (test_lookup_instances (true,
+                                           1,
+                                           &instances[1]));
+  return 0;
+}
+
 
 /**
- * A mock contract, not need to be well-formed
+ * Function that tests instances.
+ *
+ * @return 0 on success, 1 otherwise.
  */
-static json_t *contract;
+static int
+test_instances (void)
+{
+  struct TestInstances_Closure test_cls;
+  pre_test_instances (&test_cls);
+  int test_result = run_test_instances (&test_cls);
+  post_test_instances (&test_cls);
+  return test_result;
+}
+
+
+/* *********** Products ********** */
+
 
 /**
- * Mock proposal data, not need to be well-formed
+ * Frees memory associated with a product.
+ *
+ * @param pd the product containing the memory to free.
  */
-static json_t *contract_terms;
+static void
+free_product (struct TALER_MERCHANTDB_ProductDetails *pd)
+{
+  if (NULL != pd->description)
+    GNUNET_free (pd->description);
+  if (NULL != pd->description_i18n)
+    json_decref (pd->description_i18n);
+  if (NULL != pd->unit)
+    GNUNET_free (pd->unit);
+  if (NULL != pd->image)
+    json_decref (pd->image);
+  if (NULL != pd->address)
+    json_decref (pd->address);
+}
+
 
 /**
- * Mock proposal data, not need to be well-formed
+ * A container for data relevant to a product.
  */
-static json_t *contract_terms_future;
+struct ProductData
+{
+  /**
+   * The identifier of the product.
+   */
+  const char *id;
+
+  /**
+   * The details of the product.
+   */
+  struct TALER_MERCHANTDB_ProductDetails product;
+};
 
 
 /**
- * Function called with information about a refund.
+ * Creates a product for testing with.
  *
- * @param cls closure
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued the @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
+ * @param id the id of the product.
+ * @param product the product data to fill.
  */
 static void
-refund_cb (void *cls,
-           const struct TALER_CoinSpendPublicKeyP *coin_pub,
-           const char *exchange_url,
-           uint64_t rtransaction_id,
-           const char *reason,
-           const struct TALER_Amount *refund_amount,
-           const struct TALER_Amount *refund_fee)
+make_product (const char *id,
+              struct ProductData *product)
 {
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "refund_cb\n");
-  /* FIXME, more logic here? */
+  product->id = id;
+  product->product.description = "This is a test product";
+  product->product.description_i18n = json_array ();
+  GNUNET_assert (NULL != product->product.description_i18n);
+  product->product.unit = "boxes";
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:120.40",
+                                         &product->product.price));
+  product->product.taxes = json_array ();
+  GNUNET_assert (NULL != product->product.taxes);
+  product->product.total_stock = 55;
+  product->product.total_sold = 0;
+  product->product.total_lost = 0;
+  product->product.image = json_array ();
+  GNUNET_assert (NULL != product->product.image);
+  product->product.address = json_array ();
+  GNUNET_assert (NULL != product->product.address);
+  product->product.next_restock = GNUNET_TIME_absolute_get_zero_ ();
 }
 
 
 /**
- * Callback for `find_contract_terms_by_date`.
+ * Frees memory associated with @e ProductData.
  *
- * @param cls closure
- * @param order_id order id
- * @param row_id row id in db
- * @param contract_terms proposal data
+ * @param product the container to free.
  */
 static void
-pd_cb (void *cls,
-       const char *order_id,
-       uint64_t row_id,
-       const json_t *contract_terms)
+free_product_data (struct ProductData *product)
 {
-  return;
+  json_decref (product->product.description_i18n);
+  json_decref (product->product.taxes);
+  json_decref (product->product.image);
+  json_decref (product->product.address);
 }
 
 
-#define CHECK(a) do { if (! (a)) { GNUNET_break (0); result = 3; } } while (0)
-
-
 /**
- * Function called with information about a coin that was deposited.
+ * Compare two products for equality.
  *
- * @param cls closure
- * @param transaction_id of the contract
- * @param acoin_pub public key of the coin
- * @param aexchange_url exchange associated with @a acoin_pub in DB
- * @param aamount_with_fee amount the exchange will deposit for this coin
- * @param adeposit_fee fee the exchange will charge for this coin
- * @param adeposit_fee fee the exchange will charge for refunding this coin
- * @param exchange_proof proof from exchange that coin was accepted
- */
-static void
-deposit_cb (void *cls,
-            const struct GNUNET_HashCode *ah_contract_terms,
-            const struct TALER_CoinSpendPublicKeyP *acoin_pub,
-            const char *aexchange_url,
-            const struct TALER_Amount *aamount_with_fee,
-            const struct TALER_Amount *adeposit_fee,
-            const struct TALER_Amount *arefund_fee,
-            const struct TALER_Amount *awire_fee,
-            const json_t *aexchange_proof)
-{
-  CHECK (0 == GNUNET_memcmp (ah_contract_terms,
-                             &h_contract_terms));
-  CHECK (0 == GNUNET_memcmp (acoin_pub,
-                             &coin_pub));
-  CHECK (0 == strcmp (aexchange_url,
-                      EXCHANGE_URL));
-  CHECK (0 == TALER_amount_cmp (aamount_with_fee,
-                                &amount_with_fee));
-  CHECK (0 == TALER_amount_cmp (adeposit_fee,
-                                &deposit_fee));
-  CHECK (0 == TALER_amount_cmp (awire_fee,
-                                &wire_fee));
-  CHECK (1 == json_equal ((json_t *) aexchange_proof,
-                          deposit_proof));
-}
-
-
-/**
- * Information about the wire transfer corresponding to
- * a deposit operation.  Note that it is in theory possible
- * that we have a @a transaction_id and @a coin_pub in the
- * result that do not match a deposit that we know about,
- * for example because someone else deposited funds into
- * our account.
- *
- * @param cls closure
- * @param transaction_id ID of the contract
- * @param coin_pub public key of the coin
- * @param wtid identifier of the wire transfer in which the exchange
- *             send us the money for the coin deposit
- * @param execution_time when was the @a wtid transfer executed
- * @param exchange_proof proof from exchange about what the deposit was for
- *             NULL if we have not asked for this signature
+ * @param a the first product.
+ * @param b the second product.
+ * @return 0 on equality, 1 otherwise.
  */
-static void
-transfer_cb (void *cls,
-             const struct GNUNET_HashCode *ah_contract_terms,
-             const struct TALER_CoinSpendPublicKeyP *acoin_pub,
-             const struct TALER_WireTransferIdentifierRawP *awtid,
-             struct GNUNET_TIME_Absolute execution_time,
-             const json_t *exchange_proof)
+static int
+check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a,
+                      const struct TALER_MERCHANTDB_ProductDetails *b)
 {
-  CHECK (0 == GNUNET_memcmp (ah_contract_terms,
-                             &h_contract_terms));
-
-  CHECK (0 == GNUNET_memcmp (acoin_pub,
-                             &coin_pub));
-  CHECK (0 == GNUNET_memcmp (awtid,
-                             &wtid));
-  CHECK (1 == json_equal ((json_t *) exchange_proof,
-                          transfer_proof));
+  if ((0 != strcmp (a->description,
+                    b->description)) ||
+      (1 != json_equal (a->description_i18n,
+                        b->description_i18n)) ||
+      (0 != strcmp (a->unit,
+                    b->unit)) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (&a->price,
+                                               &b->price)) ||
+      (0 != TALER_amount_cmp (&a->price,
+                              &b->price)) ||
+      (1 != json_equal (a->taxes,
+                        b->taxes)) ||
+      (a->total_stock != b->total_stock) ||
+      (a->total_sold != b->total_sold) ||
+      (a->total_lost != b->total_lost) ||
+      (1 != json_equal (a->image,
+                        b->image)) ||
+      (1 != json_equal (a->address,
+                        b->address)) ||
+      (a->next_restock.abs_value_us != b->next_restock.abs_value_us))
+    return 1;
+  return 0;
 }
 
 
 /**
- * Function called with information about a wire transfer identifier.
+ * Tests inserting product data into the database.
  *
- * @param cls closure
- * @param proof proof from exchange about what the wire transfer was for
+ * @param instance the instance to insert the product for.
+ * @param product the product data to insert.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
  */
-static void
-proof_cb (void *cls,
-          const json_t *proof)
+static int
+test_insert_product (const struct InstanceData *instance,
+                     const struct ProductData *product,
+                     enum GNUNET_DB_QueryStatus expected_result)
 {
-  CHECK (1 == json_equal ((json_t *) proof,
-                          transfer_proof));
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_product (plugin->cls,
+                                                 instance->instance.id,
+                                                 product->id,
+                                                 &product->product),
+                         "Insert product failed\n");
+  return 0;
 }
 
 
-#undef CHECK
+/**
+ * Tests updating product data in the database.
+ *
+ * @param instance the instance to update the product for.
+ * @param product the product data to update.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_update_product (const struct InstanceData *instance,
+                     const struct ProductData *product,
+                     enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->update_product (plugin->cls,
+                                                 instance->instance.id,
+                                                 product->id,
+                                                 &product->product),
+                         "Update product failed\n");
+  return 0;
+}
 
 
 /**
- * Test the wire fee storage.
+ * Tests looking up a product from the db.
  *
- * @return #GNUNET_OK on success
+ * @param instance the instance to query from.
+ * @param product the product to query and compare to.
+ * @return 0 when successful, 1 otherwise.
  */
 static int
-test_wire_fee ()
+test_lookup_product (const struct InstanceData *instance,
+                     const struct ProductData *product)
 {
-  struct TALER_MasterPublicKeyP exchange_pub;
-  struct GNUNET_HashCode h_wire_method;
-  struct GNUNET_TIME_Absolute contract_date;
-  struct TALER_Amount wire_fee1;
-  struct TALER_Amount closing_fee1;
-  struct TALER_Amount wire_fee2;
-  struct TALER_Amount closing_fee2;
-  struct TALER_Amount wire_fee3;
-  struct TALER_Amount closing_fee3;
-  struct GNUNET_TIME_Absolute date1;
-  struct GNUNET_TIME_Absolute date2;
-  struct GNUNET_TIME_Absolute date3;
-  struct GNUNET_TIME_Absolute start_date;
-  struct GNUNET_TIME_Absolute end_date;
-  struct TALER_MasterSignatureP exchange_sig;
-  struct TALER_MasterSignatureP exchange_sig2;
-
-  RND_BLK (&exchange_pub);
-  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,
-                                    GNUNET_TIME_UNIT_DAYS);
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":5",
-                                         &closing_fee1));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":4",
-                                         &wire_fee1));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":3",
-                                         &closing_fee2));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":2",
-                                         &wire_fee2));
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-      plugin->store_wire_fee_by_exchange (plugin->cls,
-                                          &exchange_pub,
-                                          &h_wire_method,
-                                          &wire_fee1,
-                                          &closing_fee1,
-                                          date1,
-                                          date2,
-                                          &exchange_sig))
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-      plugin->store_wire_fee_by_exchange (plugin->cls,
-                                          &exchange_pub,
-                                          &h_wire_method,
-                                          &wire_fee2,
-                                          &closing_fee2,
-                                          date2,
-                                          date3,
-                                          &exchange_sig))
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  contract_date = date2; /* test inclusive/exclusive range */
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-      plugin->lookup_wire_fee (plugin->cls,
-                               &exchange_pub,
-                               &h_wire_method,
-                               contract_date,
-                               &wire_fee3,
-                               &closing_fee3,
-                               &start_date,
-                               &end_date,
-                               &exchange_sig2))
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  if ( (start_date.abs_value_us != date2.abs_value_us) ||
-       (end_date.abs_value_us != date3.abs_value_us) ||
-       (0 != GNUNET_memcmp (&exchange_sig,
-                            &exchange_sig2)) ||
-       (0 != TALER_amount_cmp (&wire_fee2,
-                               &wire_fee3)) ||
-       (0 != TALER_amount_cmp (&closing_fee2,
-                               &closing_fee3)) )
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  contract_date = GNUNET_TIME_absolute_add (date1,
-                                            GNUNET_TIME_UNIT_SECONDS);
-  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-      plugin->lookup_wire_fee (plugin->cls,
-                               &exchange_pub,
-                               &h_wire_method,
-                               contract_date,
-                               &wire_fee3,
-                               &closing_fee3,
-                               &start_date,
-                               &end_date,
-                               &exchange_sig2))
+  struct TALER_MERCHANTDB_ProductDetails lookup_result;
+  if (0 > plugin->lookup_product (plugin->cls,
+                                  instance->instance.id,
+                                  product->id,
+                                  &lookup_result))
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup product failed\n");
+    free_product (&lookup_result);
+    return 1;
   }
-  if ( (start_date.abs_value_us != date1.abs_value_us) ||
-       (end_date.abs_value_us != date2.abs_value_us) ||
-       (0 != GNUNET_memcmp (&exchange_sig,
-                            &exchange_sig2)) ||
-       (0 != TALER_amount_cmp (&wire_fee1,
-                               &wire_fee3)) ||
-       (0 != TALER_amount_cmp (&closing_fee1,
-                               &closing_fee3)) )
+  const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product;
+  if (0 != check_products_equal (&lookup_result,
+                                 to_cmp))
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup product failed: incorrect product returned\n");
+    free_product (&lookup_result);
+    return 1;
   }
-  contract_date = date3; /* outside of valid range! */
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
-      plugin->lookup_wire_fee (plugin->cls,
-                               &exchange_pub,
-                               &h_wire_method,
-                               contract_date,
-                               &wire_fee3,
-                               &closing_fee3,
-                               &start_date,
-                               &end_date,
-                               &exchange_sig2))
+  free_product (&lookup_result);
+  return 0;
+}
+
+
+/**
+ * Closure for testing product lookup
+ */
+struct TestLookupProducts_Closure
+{
+  /**
+   * Number of product ids to compare to
+   */
+  unsigned int products_to_cmp_length;
+
+  /**
+   * Pointer to array of product ids
+   */
+  const struct ProductData *products_to_cmp;
+
+  /**
+   * Pointer to array of number of matches for each product
+   */
+  unsigned int *results_matching;
+
+  /**
+   * Total number of results returned
+   */
+  unsigned int results_length;
+};
+
+
+/**
+ * Function called after calling @e test_lookup_products
+ *
+ * @param cls a pointer to the lookup closure.
+ * @param product_id the identifier of the product found.
+ */
+static void
+lookup_products_cb (void *cls,
+                    const char *product_id)
+{
+  struct TestLookupProducts_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  for (unsigned int i = 0; cmp->products_to_cmp_length > i; ++i)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    if (0 == strcmp (cmp->products_to_cmp[i].id,
+                     product_id))
+      cmp->results_matching[i] += 1;
   }
-  return GNUNET_OK;
 }
 
 
 /**
- * Test APIs related to tipping.
+ * Tests looking up all products for an instance.
  *
- * @return #GNUNET_OK upon success
+ * @param instance the instance to query from.
+ * @param products_length the number of products we are expecting.
+ * @param products the list of products that we expect to be found.
+ * @return 0 when successful, 1 otherwise.
  */
 static int
-test_tipping ()
+test_lookup_products (const struct InstanceData *instance,
+                      unsigned int products_length,
+                      const struct ProductData *products)
 {
-  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 *url;
-
-  RND_BLK (&tip_reserve_priv);
-  if (TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS !=
-      plugin->authorize_tip_TR (plugin->cls,
-                                "testing tips reserve unknown",
-                                json_object (),
-                                &amount,
-                                &tip_reserve_priv,
-                                "http://localhost:8081/";,
-                                &tip_expiration,
-                                &tip_id))
+  unsigned int results_matching[products_length];
+  struct TestLookupProducts_Closure cls = {
+    .products_to_cmp_length = products_length,
+    .products_to_cmp = products,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching, 0, sizeof (unsigned int) * products_length);
+  if (0 > plugin->lookup_products (plugin->cls,
+                                   instance->instance.id,
+                                   &lookup_products_cb,
+                                   &cls))
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup products failed\n");
+    return 1;
   }
-  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_TR (plugin->cls,
-                                     &tip_reserve_priv,
-                                     &tip_credit_uuid,
-                                     &total,
-                                     reserve_expiration))
+  if (products_length != cls.results_length)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup products failed: incorrect number of results\n");
+    return 1;
   }
-  /* check idempotency */
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
-      plugin->enable_tip_reserve_TR (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_TR (plugin->cls,
-                                "testing tips too late",
-                                json_object (),
-                                &amount,
-                                &tip_reserve_priv,
-                                "http://localhost:8081/";,
-                                &tip_expiration,
-                                &tip_id))
+  for (unsigned int i = 0; products_length > i; ++i)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    if (1 != cls.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup products failed: mismatched data\n");
+      return 1;
+    }
   }
+  return 0;
+}
 
-  /* 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_TR (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_TR (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_TR (plugin->cls,
-                                "testing tips",
-                                json_object (),
-                                &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,
-                                &url,
-                                NULL, NULL, NULL, NULL))
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  if (0 != strcmp ("http://localhost:8081/";,
-                   url))
-  {
-    GNUNET_free (url);
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_free (url);
-  if (TALER_EC_NONE !=
-      plugin->authorize_tip_TR (plugin->cls,
-                                "testing tips more",
-                                json_object (),
-                                &amount,
-                                &tip_reserve_priv,
-                                "http://localhost:8081/";,
-                                &tip_expiration,
-                                &tip_id))
+/**
+ * Tests deleting a product.
+ *
+ * @param instance the instance to delete the product from.
+ * @param product the product that should be deleted.
+ * @param expected_result the result that we expect the plugin to return.
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_delete_product (const struct InstanceData *instance,
+                     const struct ProductData *product,
+                     enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->delete_product (plugin->cls,
+                                                 instance->instance.id,
+                                                 product->id),
+                         "Delete product failed\n");
+  return 0;
+}
+
+
+/**
+ * Closure for product tests.
+ */
+struct TestProducts_Closure
+{
+  /**
+   * The instance to use for this test.
+   */
+  struct InstanceData instance;
+
+  /**
+   * The array of products.
+   */
+  struct ProductData products[2];
+};
+
+
+/**
+ * Sets up the data structures used in the product tests.
+ *
+ * @param cls the closure to fill with test data.
+ */
+static void
+pre_test_products (struct TestProducts_Closure *cls)
+{
+  /* Instance */
+  make_instance ("test_inst_products",
+                 &cls->instance);
+
+  /* Products */
+  make_product ("test_products_pd_0",
+                &cls->products[0]);
+
+  make_product ("test_products_pd_1",
+                &cls->products[1]);
+  cls->products[1].product.description = "This is a another test product";
+  cls->products[1].product.unit = "cans";
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:4.95",
+                                         &cls->products[1].product.price));
+  cls->products[1].product.total_stock = 5001;
+}
+
+
+/**
+ * Handles all teardown after testing.
+ *
+ * @param cls the closure containing memory to be freed.
+ */
+static void
+post_test_products (struct TestProducts_Closure *cls)
+{
+  free_instance_data (&cls->instance);
+  free_product_data (&cls->products[0]);
+  free_product_data (&cls->products[1]);
+}
+
+
+/**
+ * Runs the tests for products.
+ *
+ * @param cls the container of the test data.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+run_test_products (struct TestProducts_Closure *cls)
+{
+  struct GNUNET_Uuid uuid;
+  struct GNUNET_TIME_Absolute refund_deadline =
+    GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+                              GNUNET_TIME_UNIT_WEEKS);
+
+  /* Test that insert without an instance fails */
+  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
+                                         &cls->products[0],
+                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Insert the instance */
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test inserting a product */
+  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
+                                         &cls->products[0],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test that double insert fails */
+  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
+                                         &cls->products[0],
+                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test lookup of individual products */
+  TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
+                                         &cls->products[0]));
+  /* Make sure it fails correctly for products that don't exist */
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+      plugin->lookup_product (plugin->cls,
+                              cls->instance.instance.id,
+                              "nonexistent_product",
+                              NULL))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup product failed\n");
+    return 1;
+  }
+  /* Test product update */
+  cls->products[0].product.description =
+    "This is a test product that has been updated!";
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   cls->products[0].product.description_i18n,
+                   json_string (
+                     "description in another language")));
+  cls->products[0].product.unit = "barrels";
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:7.68",
+                                         &cls->products[0].product.price));
+  GNUNET_assert (0 ==
+                 json_array_append_new (cls->products[0].product.taxes,
+                                        json_string ("2% sales tax")));
+  cls->products[0].product.total_stock = 100;
+  cls->products[0].product.total_sold = 10;
+  cls->products[0].product.total_lost = 7;
+  GNUNET_assert (0 ==
+                 json_array_append_new (cls->products[0].product.image,
+                                        json_string (
+                                          
"http://some-website.com/image.png";)));
+  GNUNET_assert (0 ==
+                 json_array_append_new (cls->products[0].product.address,
+                                        json_string ("444 Some Street")));
+  cls->products[0].product.next_restock = GNUNET_TIME_absolute_get ();
+  TEST_RET_ON_FAIL (test_update_product (&cls->instance,
+                                         &cls->products[0],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+
+  {
+    struct ProductData stock_dec = cls->products[0];
+    stock_dec.product.total_stock = 40;
+    TEST_RET_ON_FAIL (test_update_product (&cls->instance,
+                                           &stock_dec,
+                                           
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  }
+  {
+    struct ProductData sold_dec = cls->products[0];
+    sold_dec.product.total_sold = 5;
+    TEST_RET_ON_FAIL (test_update_product (&cls->instance,
+                                           &sold_dec,
+                                           
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  }
+  {
+    struct ProductData lost_dec = cls->products[0];
+    lost_dec.product.total_lost = 1;
+    TEST_RET_ON_FAIL (test_update_product (&cls->instance,
+                                           &lost_dec,
+                                           
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  }
+  TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
+                                         &cls->products[0]));
+  TEST_RET_ON_FAIL (test_update_product (&cls->instance,
+                                         &cls->products[1],
+                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test collective product lookup */
+  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
+                                         &cls->products[1],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
+                                          2,
+                                          cls->products));
+  /* Test locking */
+  uuid.value[0] = 0x1287346a;
+  if (0 != plugin->lock_product (plugin->cls,
+                                 cls->instance.instance.id,
+                                 cls->products[0].id,
+                                 &uuid,
+                                 256,
+                                 refund_deadline))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lock product failed\n");
+    return 1;
+  }
+  if (1 != plugin->lock_product (plugin->cls,
+                                 cls->instance.instance.id,
+                                 cls->products[0].id,
+                                 &uuid,
+                                 1,
+                                 refund_deadline))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lock product failed\n");
+    return 1;
+  }
+  /* Test product deletion */
+  TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
+                                         &cls->products[1],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test double deletion fails */
+  TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
+                                         &cls->products[1],
+                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
+                                          1,
+                                          cls->products));
+  /* Test unlocking */
+  if (1 != plugin->unlock_inventory (plugin->cls,
+                                     &uuid))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unlock inventory failed\n");
+    return 1;
+  }
+  if (0 != plugin->unlock_inventory (plugin->cls,
+                                     &uuid))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unlock inventory failed\n");
+    return 1;
+  }
+  TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
+                                         &cls->products[0],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
+                                          0,
+                                          NULL));
+  return 0;
+}
+
+
+/**
+ * Takes care of product testing.
+ *
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_products (void)
+{
+  struct TestProducts_Closure test_cls;
+  pre_test_products (&test_cls);
+  int test_result = run_test_products (&test_cls);
+  post_test_products (&test_cls);
+  return test_result;
+}
+
+
+/* ********** Orders ********** */
+
+
+/**
+ * Container for order data
+ */
+struct OrderData
+{
+  /**
+   * The id of the order
+   */
+  const char *id;
+
+  /**
+   * The pay deadline for the order
+   */
+  struct GNUNET_TIME_Absolute pay_deadline;
+
+  /**
+   * The contract of the order
+   */
+  json_t *contract;
+};
+
+
+/**
+ * Builds an order for testing.
+ *
+ * @param order_id the identifier to use for the order.
+ * @param order the container to fill with data.
+ */
+static void
+make_order (const char *order_id,
+            struct OrderData *order)
+{
+  struct GNUNET_TIME_Absolute pay_deadline;
+  struct GNUNET_TIME_Absolute refund_deadline;
+
+  order->id = order_id;
+  order->pay_deadline = GNUNET_TIME_absolute_get_zero_ ();
+  order->contract = json_object ();
+  GNUNET_assert (NULL != order->contract);
+  pay_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+                                           GNUNET_TIME_UNIT_DAYS);
+  refund_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+                                              GNUNET_TIME_UNIT_WEEKS);
+  GNUNET_TIME_round_abs (&pay_deadline);
+  GNUNET_TIME_round_abs (&refund_deadline);
+  json_object_set_new (order->contract,
+                       "fulfillment_url",
+                       json_string ("a"));
+  json_object_set_new (order->contract,
+                       "order_id",
+                       json_string (order_id));
+  json_object_set_new (order->contract,
+                       "pay_deadline",
+                       GNUNET_JSON_from_time_abs (pay_deadline));
+  json_object_set_new (order->contract,
+                       "refund_deadline",
+                       GNUNET_JSON_from_time_abs (refund_deadline));
+}
+
+
+/**
+ * Frees memory associated with an order.
+ *
+ * @param the order to free.
+ */
+static void
+free_order_data (struct OrderData *order)
+{
+  json_decref (order->contract);
+}
+
+
+/**
+ * Tests inserting an order into the database.
+ *
+ * @param instance the instance to insert the order for.
+ * @param order the order to insert.
+ * @param expected_result the value we expect the db to return.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_order (const struct InstanceData *instance,
+                   const struct OrderData *order,
+                   enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_order (plugin->cls,
+                                               instance->instance.id,
+                                               order->id,
+                                               order->pay_deadline,
+                                               order->contract),
+                         "Insert order failed\n");
+  return 0;
+}
+
+
+/**
+ * Tests looking up an order in the database.
+ *
+ * @param instance the instance to lookup from.
+ * @param order the order that should be looked up.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_order (const struct InstanceData *instance,
+                   const struct OrderData *order)
+{
+  json_t *lookup_terms = NULL;
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->lookup_order (plugin->cls,
+                            instance->instance.id,
+                            order->id,
+                            &lookup_terms))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup order failed\n");
+    if (NULL != lookup_terms)
+      json_decref (lookup_terms);
+    return 1;
+  }
+  if (1 != json_equal (order->contract,
+                       lookup_terms))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup order failed: incorrect order returned\n");
+    if (NULL != lookup_terms)
+      json_decref (lookup_terms);
+    return 1;
+  }
+  json_decref (lookup_terms);
+  return 0;
+}
+
+
+/**
+ * Closure for testing order lookup
+ */
+struct TestLookupOrders_Closure
+{
+  /**
+   * Number of orders to compare to
+   */
+  unsigned int orders_to_cmp_length;
+
+  /**
+   * Pointer to (ordered) array of order ids
+   */
+  const struct OrderData *orders_to_cmp;
+
+  /**
+   * Pointer to array of bools indicating matches in the correct index
+   */
+  bool *results_match;
+
+  /**
+   * Total number of results returned
+   */
+  unsigned int results_length;
+};
+
+
+/**
+ * Called after @e test_lookup_orders.
+ *
+ * @param cls the lookup closure.
+ * @param order_id the identifier of the order found.
+ * @param order_serial the row number of the order found.
+ * @param timestamp when the order was added to the database.
+ */
+static void
+lookup_orders_cb (void *cls,
+                  const char *order_id,
+                  uint64_t order_serial,
+                  struct GNUNET_TIME_Absolute timestamp)
+{
+  struct TestLookupOrders_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  unsigned int i = cmp->results_length;
+  cmp->results_length += 1;
+  if (cmp->orders_to_cmp_length > i)
+  {
+    /* Compare the orders */
+    if (0 == strcmp (cmp->orders_to_cmp[i].id,
+                     order_id))
+      cmp->results_match[i] = true;
+    else
+      cmp->results_match[i] = false;
+  }
+}
+
+
+/**
+ * Tests looking up orders for an instance.
+ *
+ * @param instance the instance.
+ * @param filter the filters applied on the lookup.
+ * @param orders_length the number of orders we expect to find.
+ * @param orders the orders we expect to find.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_orders (const struct InstanceData *instance,
+                    const struct TALER_MERCHANTDB_OrderFilter *filter,
+                    unsigned int orders_length,
+                    const struct OrderData *orders)
+{
+  bool results_match[orders_length];
+  struct TestLookupOrders_Closure cls = {
+    .orders_to_cmp_length = orders_length,
+    .orders_to_cmp = orders,
+    .results_match = results_match,
+    .results_length = 0
+  };
+  memset (results_match,
+          0,
+          sizeof (bool) * orders_length);
+  if (0 > plugin->lookup_orders (plugin->cls,
+                                 instance->instance.id,
+                                 filter,
+                                 &lookup_orders_cb,
+                                 &cls))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup orders failed\n");
+    return 1;
+  }
+  if (orders_length != cls.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup orders failed: incorrect number of results (%d)\n",
+                cls.results_length);
+    return 1;
+  }
+  for (unsigned int i = 0; orders_length > i; ++i)
+  {
+    if (false == cls.results_match[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup orders failed: mismatched data (index %d)\n",
+                  i);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Container for data used for looking up the row number of an order.
+ */
+struct LookupOrderSerial_Closure
+{
+  /**
+   * The order that is being looked up.
+   */
+  const struct OrderData *order;
+
+  /**
+   * The serial of the order that was found.
+   */
+  uint64_t serial;
+};
+
+
+/**
+ * Called after @e test_lookup_orders.
+ *
+ * @param cls the lookup closure.
+ * @param order_id the identifier of the order found.
+ * @param order_serial the row number of the order found.
+ * @param timestamp when the order was added to the database.
+ */
+static void
+get_order_serial_cb (void *cls,
+                     const char *order_id,
+                     uint64_t order_serial,
+                     struct GNUNET_TIME_Absolute timestamp)
+{
+  struct LookupOrderSerial_Closure *lookup_cls = cls;
+  if (NULL == lookup_cls)
+    return;
+  if (0 == strcmp (lookup_cls->order->id,
+                   order_id))
+    lookup_cls->serial = order_serial;
+}
+
+
+/**
+ * Convenience function for getting the row number of an order.
+ *
+ * @param instance the instance to look up from.
+ * @param order the order to lookup the serial for.
+ * @return the row number of the order.
+ */
+static uint64_t
+get_order_serial (const struct InstanceData *instance,
+                  const struct OrderData *order)
+{
+  struct LookupOrderSerial_Closure lookup_cls = {
+    .order = order,
+    .serial = 0
+  };
+  struct TALER_MERCHANTDB_OrderFilter filter = {
+    .paid = TALER_EXCHANGE_YNA_ALL,
+    .refunded = TALER_EXCHANGE_YNA_ALL,
+    .wired = TALER_EXCHANGE_YNA_ALL,
+    .date = GNUNET_TIME_UNIT_ZERO_ABS,
+    .start_row = 0,
+    .delta = 256
+  };
+
+  GNUNET_assert (0 < plugin->lookup_orders (plugin->cls,
+                                            instance->instance.id,
+                                            &filter,
+                                            &get_order_serial_cb,
+                                            &lookup_cls));
+  GNUNET_assert (0 != lookup_cls.serial);
+
+  return lookup_cls.serial;
+}
+
+
+/**
+ * Tests deleting an order from the database.
+ *
+ * @param instance the instance to delete the order from.
+ * @param order the order to delete.
+ * @param expected_result the result we expect to receive.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_delete_order (const struct InstanceData *instance,
+                   const struct OrderData *order,
+                   enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->delete_order (plugin->cls,
+                                               instance->instance.id,
+                                               order->id),
+                         "Delete order failed\n");
+  return 0;
+}
+
+
+/**
+ * Test inserting contract terms for an order.
+ *
+ * @param instance the instance.
+ * @param order the order containing the contract terms.
+ * @param expected_result the result we expect to receive.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_contract_terms (const struct InstanceData *instance,
+                            const struct OrderData *order,
+                            enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_contract_terms (plugin->cls,
+                                                        instance->instance.id,
+                                                        order->id,
+                                                        order->contract),
+                         "Insert contract terms failed\n");
+  return 0;
+}
+
+
+/**
+ * Tests lookup of contract terms
+ *
+ * @param instance the instance to lookup from.
+ * @param order the order to lookup for.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_contract_terms (const struct InstanceData *instance,
+                            const struct OrderData *order)
+{
+  json_t *contract = NULL;
+  uint64_t order_serial;
+
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->lookup_contract_terms (plugin->cls,
+                                     instance->instance.id,
+                                     order->id,
+                                     &contract,
+                                     &order_serial))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup contract terms failed\n");
+    GNUNET_assert (NULL == contract);
+    return 1;
+  }
+  if (1 != json_equal (order->contract,
+                       contract))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup contract terms failed: mismatched data\n");
+    json_decref (contract);
+    return 1;
+  }
+  json_decref (contract);
+  return 0;
+}
+
+
+/**
+ * Tests deleting contract terms for an order.
+ *
+ * @param instance the instance to delete from.
+ * @param order the order whose contract terms we should delete.
+ * @param expected_result the result we expect to receive.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_delete_contract_terms (const struct InstanceData *instance,
+                            const struct OrderData *order,
+                            enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->delete_contract_terms (plugin->cls,
+                                                        instance->instance.id,
+                                                        order->id,
+                                                        
GNUNET_TIME_UNIT_MONTHS),
+                         "Delete contract terms failed\n");
+  return 0;
+}
+
+
+/**
+ * Test marking a contract as paid in the database.
+ *
+ * @param instance the instance to use.
+ * @param order the order whose contract to use.
+ * @param expected_result the result we expect to receive.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_mark_contract_paid (const struct InstanceData *instance,
+                         const struct OrderData *order,
+                         enum GNUNET_DB_QueryStatus expected_result)
+{
+  struct GNUNET_HashCode h_contract_terms;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_hash (order->contract,
+                                  &h_contract_terms));
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->mark_contract_paid (plugin->cls,
+                                                     instance->instance.id,
+                                                     &h_contract_terms,
+                                                     "test_orders_session"),
+                         "Mark contract paid failed\n");
+  return 0;
+}
+
+
+/**
+ * Tests looking up the status of an order.
+ *
+ * @param instance the instance to lookup from.
+ * @param order the order to lookup.
+ * @param expected_paid whether the order was paid or not.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_order_status (const struct InstanceData *instance,
+                          const struct OrderData *order,
+                          bool expected_paid)
+{
+  struct GNUNET_HashCode h_contract_terms_expected;
+  struct GNUNET_HashCode h_contract_terms;
+  bool order_paid = false;
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->lookup_order_status (plugin->cls,
+                                   instance->instance.id,
+                                   order->id,
+                                   &h_contract_terms,
+                                   &order_paid))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup order status failed\n");
+    return 1;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_hash (order->contract,
+                                  &h_contract_terms_expected));
+  if ((expected_paid != order_paid) ||
+      (0 != GNUNET_memcmp (&h_contract_terms,
+                           &h_contract_terms_expected)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup order status/deposit failed: mismatched data\n");
+    return 1;
+  }
+  return 0;
+}
+
+
+/**
+ * Test looking up an order by its fulfillment.
+ *
+ * @param instance the instance to lookup from.
+ * @param order the order to lookup.
+ * @param the session id associated with the payment.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_order_by_fulfillment (const struct InstanceData *instance,
+                                  const struct OrderData *order,
+                                  const char *session_id)
+{
+  char *order_id;
+  const char *fulfillment_url =
+    json_string_value (json_object_get (order->contract,
+                                        "fulfillment_url"));
+  GNUNET_assert (NULL != fulfillment_url);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->lookup_order_by_fulfillment (plugin->cls,
+                                           instance->instance.id,
+                                           fulfillment_url,
+                                           session_id,
+                                           &order_id))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup order by fulfillment failed\n");
+    GNUNET_free (order_id);
+    return 1;
+  }
+  if (0 != strcmp (order->id,
+                   order_id))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup order by fulfillment failed\n");
+    GNUNET_free (order_id);
+    return 1;
+  }
+  GNUNET_free (order_id);
+  return 0;
+}
+
+
+/**
+ * Test looking up the status of an order.
+ *
+ * @param order_id the row of the order in the database.
+ * @param session_id the session id associated with the payment.
+ * @param expected_paid whether the order was paid or not.
+ * @param expected_wired whether the order was wired or not.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_payment_status (uint64_t order_id,
+                            const char *session_id,
+                            bool expected_paid,
+                            bool expected_wired)
+{
+  bool paid;
+  bool wired;
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->lookup_payment_status (plugin->cls,
+                                                        order_id,
+                                                        session_id,
+                                                        &paid,
+                                                        &wired),
+                         "Lookup payment status failed\n");
+  if ((expected_paid != paid) ||
+      (expected_wired != wired))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup payment status failed\n");
+    return 1;
+  }
+  return 0;
+}
+
+
+/**
+ * Test marking an order as being wired.
+ *
+ * @param order_id the row of the order in the database.
+ * @param expected_result the result we expect the plugin to return.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_mark_order_wired (uint64_t order_id,
+                       enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->mark_order_wired (plugin->cls,
+                                                   order_id),
+                         "Mark order wired failed\n");
+  return 0;
+}
+
+
+/**
+ * Closure for order tests.
+ */
+struct TestOrders_Closure
+{
+  /**
+   * The instance to use for the order tests.
+   */
+  struct InstanceData instance;
+
+  /**
+   * A product to use for the order tests.
+   */
+  struct ProductData product;
+
+  /**
+   * The array of orders
+   */
+  struct OrderData orders[2];
+};
+
+
+/**
+ * Initializes order test data.
+ *
+ * @param cls the order test closure.
+ */
+static void
+pre_test_orders (struct TestOrders_Closure *cls)
+{
+  /* Instance */
+  make_instance ("test_inst_orders",
+                 &cls->instance);
+
+  /* Product */
+  make_product ("test_orders_pd_0",
+                &cls->product);
+
+  /* Orders */
+  make_order ("test_orders_od_0",
+              &cls->orders[0]);
+  make_order ("test_orders_od_1",
+              &cls->orders[1]);
+  GNUNET_assert (0 == json_object_set_new (cls->orders[1].contract,
+                                           "other_field",
+                                           json_string ("Second contract")));
+}
+
+
+/**
+ * Frees memory after order tests.
+ *
+ * @param cls the order test closure.
+ */
+static void
+post_test_orders (struct TestOrders_Closure *cls)
+{
+  free_instance_data (&cls->instance);
+  free_product_data (&cls->product);
+  free_order_data (&cls->orders[0]);
+  free_order_data (&cls->orders[1]);
+}
+
+
+/**
+ * Run the tests for orders.
+ *
+ * @param cls the order test closure.
+ * @return 0 on success, 1 on failure.
+ */
+static int
+run_test_orders (struct TestOrders_Closure *cls)
+{
+  struct TALER_MERCHANTDB_OrderFilter filter = {
+    .paid = TALER_EXCHANGE_YNA_ALL,
+    .refunded = TALER_EXCHANGE_YNA_ALL,
+    .wired = TALER_EXCHANGE_YNA_ALL,
+    .date = GNUNET_TIME_absolute_get_zero_ (),
+    .start_row = 0,
+    .delta = 8
+  };
+  uint64_t serial;
+
+  /* Insert the instance */
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test inserting an order */
+  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                       &cls->orders[0],
+                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test double insert fails */
+  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                       &cls->orders[0],
+                                       GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test lookup order */
+  TEST_RET_ON_FAIL (test_lookup_order (&cls->instance,
+                                       &cls->orders[0]));
+  /* Make sure it fails correctly for nonexistent orders */
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+      plugin->lookup_order (plugin->cls,
+                            cls->instance.instance.id,
+                            cls->orders[1].id,
+                            NULL))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup order failed\n");
+    return 1;
+  }
+  /* Test lookups on multiple orders */
+  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                       &cls->orders[1],
+                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  serial = get_order_serial (&cls->instance,
+                             &cls->orders[0]);
+  TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
+                                        &filter,
+                                        2,
+                                        cls->orders));
+  /* Test inserting contract terms */
+  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
+                                                &cls->orders[0],
+                                                
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test double insert fails */
+  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
+                                                &cls->orders[0],
+                                                
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test order lock */
+  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
+                                         &cls->product,
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  if (1 != plugin->insert_order_lock (plugin->cls,
+                                      cls->instance.instance.id,
+                                      cls->orders[0].id,
+                                      cls->product.id,
+                                      5))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Insert order lock failed\n");
+    return 1;
+  }
+  /* Test lookup contract terms */
+  TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance,
+                                                &cls->orders[0]));
+  /* Test lookup fails for nonexistent contract terms */
+  {
+    json_t *lookup_contract = NULL;
+    uint64_t lookup_order_serial;
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+        plugin->lookup_contract_terms (plugin->cls,
+                                       cls->instance.instance.id,
+                                       cls->orders[1].id,
+                                       &lookup_contract,
+                                       &lookup_order_serial))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup contract terms failed\n");
+      GNUNET_assert (NULL == lookup_contract);
+      return 1;
+    }
+  }
+  /* Test lookup order status */
+  TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
+                                              &cls->orders[0],
+                                              false));
+  {
+    struct GNUNET_HashCode h_contract_terms;
+    bool order_paid = false;
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+        plugin->lookup_order_status (plugin->cls,
+                                     cls->instance.instance.id,
+                                     cls->orders[1].id,
+                                     &h_contract_terms,
+                                     &order_paid))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup order status failed\n");
+      return 1;
+    }
+  }
+  /* Test lookup payment status */
+  TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+                                                NULL,
+                                                false,
+                                                false));
+  {
+    bool paid;
+    bool wired;
+    TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+                           plugin->lookup_payment_status (plugin->cls,
+                                                          256,
+                                                          NULL,
+                                                          &paid,
+                                                          &wired),
+                           "Lookup payment status failed\n");
+  }
+  /* Test lookup order status fails for nonexistent order */
+  {
+    struct GNUNET_HashCode h_contract_terms;
+    bool order_paid;
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+        plugin->lookup_order_status (plugin->cls,
+                                     cls->instance.instance.id,
+                                     cls->orders[1].id,
+                                     &h_contract_terms,
+                                     &order_paid))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup order status failed\n");
+      return 1;
+    }
+  }
+  /* Test marking contracts as paid */
+  TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
+                                             &cls->orders[0],
+                                             
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+                                                NULL,
+                                                true,
+                                                false));
+  TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+                                                "test_orders_session",
+                                                true,
+                                                false));
+  {
+    bool paid;
+    bool wired;
+    TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+                           plugin->lookup_payment_status (plugin->cls,
+                                                          serial,
+                                                          "bad_session",
+                                                          &paid,
+                                                          &wired),
+                           "Lookup payment status failed\n");
+  }
+  /* Test lookup order by fulfillment */
+  TEST_RET_ON_FAIL (test_lookup_order_by_fulfillment (&cls->instance,
+                                                      &cls->orders[0],
+                                                      "test_orders_session"));
+  {
+    char *order_id;
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+        plugin->lookup_order_by_fulfillment (plugin->cls,
+                                             cls->instance.instance.id,
+                                             "fulfillment_url",
+                                             "test_orders_session",
+                                             &order_id))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup order by fulfillment failed\n");
+      GNUNET_free (order_id);
+      return 1;
+    }
+  }
+  /* Test mark as paid fails for nonexistent order */
+  TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
+                                             &cls->orders[1],
+                                             
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
+                                              &cls->orders[0],
+                                              true));
+  filter.paid = TALER_EXCHANGE_YNA_YES;
+  TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
+                                        &filter,
+                                        1,
+                                        cls->orders));
+  /* Test marking orders as wired */
+  TEST_RET_ON_FAIL (test_mark_order_wired (serial,
+                                           
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_payment_status (serial,
+                                                NULL,
+                                                true,
+                                                true));
+  TEST_RET_ON_FAIL (test_mark_order_wired (1007,
+                                           
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test deleting contract terms */
+  TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
+                                                &cls->orders[0],
+                                                
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test we can't delete something that doesn't exist */
+  TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
+                                                &cls->orders[0],
+                                                
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test delete order */
+  TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
+                                       &cls->orders[1],
+                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
+                                        &filter,
+                                        0,
+                                        NULL));
+  /* Test we can't delete something that doesn't exist */
+  TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
+                                       &cls->orders[1],
+                                       GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+
+  return 0;
+}
+
+
+/**
+ * Does all tasks for testing orders.
+ *
+ * @return 0 when successful, 1 otherwise.
+ */
+static int
+test_orders (void)
+{
+  struct TestOrders_Closure test_cls;
+  pre_test_orders (&test_cls);
+  int test_result = run_test_orders (&test_cls);
+  post_test_orders (&test_cls);
+  return test_result;
+}
+
+
+/* ********** Deposits ********** */
+
+
+/**
+ * A container for exchange signing key data.
+ */
+struct ExchangeSignkeyData
+{
+  /**
+   * The master private key of the exchange.
+   */
+  struct TALER_MasterPrivateKeyP master_priv;
+
+  /**
+   * The master public key of the exchange.
+   */
+  struct TALER_MasterPublicKeyP master_pub;
+
+  /**
+   * A signature made with the master keys.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+  /**
+   * The private key of the exchange.
+   */
+  struct TALER_ExchangePrivateKeyP exchange_priv;
+
+  /**
+   * The public key of the exchange.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * When the signing key becomes valid.
+   */
+  struct GNUNET_TIME_Absolute start_date;
+
+  /**
+   * When the signing key stops being used.
+   */
+  struct GNUNET_TIME_Absolute expire_date;
+
+  /**
+   * When the signing key becomes invalid for proof.
+   */
+  struct GNUNET_TIME_Absolute end_date;
+};
+
+
+/**
+ * Creates an exchange signing key.
+ *
+ * @param signkey the signing key data to fill.
+ */
+static void
+make_exchange_signkey (struct ExchangeSignkeyData *signkey)
+{
+  struct TALER_ExchangeSigningKeyValidityPS exch_sign = {
+    .purpose = {
+      .size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)),
+      .purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY)
+    }
+
+
+  };
+
+  GNUNET_CRYPTO_eddsa_key_create (&signkey->exchange_priv.eddsa_priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (&signkey->exchange_priv.eddsa_priv,
+                                      &signkey->exchange_pub.eddsa_pub);
+  GNUNET_CRYPTO_eddsa_key_create (&signkey->master_priv.eddsa_priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (&signkey->master_priv.eddsa_priv,
+                                      &signkey->master_pub.eddsa_pub);
+  GNUNET_CRYPTO_eddsa_sign (&signkey->master_priv.eddsa_priv,
+                            &exch_sign,
+                            &signkey->master_sig.eddsa_signature);
+  signkey->start_date = GNUNET_TIME_absolute_get ();
+  signkey->expire_date = GNUNET_TIME_absolute_get ();
+  signkey->end_date = GNUNET_TIME_absolute_get ();
+}
+
+
+/**
+ * A container for deposit data.
+ */
+struct DepositData
+{
+  /**
+   * When the deposit was made.
+   */
+  struct GNUNET_TIME_Absolute timestamp;
+
+  /**
+   * Hash of the associated order's contract terms.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * Public key of the coin that has been deposited.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * URL of the exchange.
+   */
+  const char *exchange_url;
+
+  /**
+   * Value of the coin with fees applied.
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Fee charged for deposit.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Fee to be charged in case of a refund.
+   */
+  struct TALER_Amount refund_fee;
+
+  /**
+   * Fee charged after the money is wired.
+   */
+  struct TALER_Amount wire_fee;
+
+  /**
+   * Hash of the wire details.
+   */
+  struct GNUNET_HashCode h_wire;
+
+  /**
+   * Signature the exchange made on this deposit.
+   */
+  struct TALER_ExchangeSignatureP exchange_sig;
+
+};
+
+
+/**
+ * Generates deposit data for an order.
+ *
+ * @param instance the instance to make the deposit to.
+ * @param account the merchant account to use.
+ * @param order the order this deposit is for.
+ * @param signkey the signing key to use.
+ * @param deposit the deposit data to fill.
+ */
+static void
+make_deposit (const struct InstanceData *instance,
+              const struct TALER_MERCHANTDB_AccountDetails *account,
+              const struct OrderData *order,
+              const struct ExchangeSignkeyData *signkey,
+              struct DepositData *deposit)
+{
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+  struct TALER_DepositRequestPS deposit_sign = {
+    .purpose = {
+      .size = htonl (sizeof (struct TALER_DepositRequestPS)),
+      .purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT)
+    }
+
+
+  };
+
+  deposit->timestamp = GNUNET_TIME_absolute_get ();
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_hash (order->contract,
+                                  &deposit->h_contract_terms));
+  GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+                                      &deposit->coin_pub.eddsa_pub);
+  deposit->exchange_url = "test-exchange";
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:50.00",
+                                         &deposit->amount_with_fee));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1.00",
+                                         &deposit->deposit_fee));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1.50",
+                                         &deposit->refund_fee));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:2.00",
+                                         &deposit->wire_fee));
+  deposit->h_wire = account->h_wire;
+  deposit_sign.h_contract_terms = deposit->h_contract_terms;
+  deposit_sign.h_wire = deposit->h_wire;
+  deposit_sign.wallet_timestamp = GNUNET_TIME_absolute_hton (
+    GNUNET_TIME_absolute_get ());
+  deposit_sign.refund_deadline = GNUNET_TIME_absolute_hton (
+    GNUNET_TIME_absolute_get ());
+  TALER_amount_hton (&deposit_sign.amount_with_fee,
+                     &deposit->amount_with_fee);
+  TALER_amount_hton (&deposit_sign.deposit_fee,
+                     &deposit->deposit_fee);
+  deposit_sign.merchant = instance->merchant_pub;
+  deposit_sign.coin_pub = deposit->coin_pub;
+  GNUNET_CRYPTO_eddsa_sign (&signkey->exchange_priv.eddsa_priv,
+                            &deposit_sign,
+                            &deposit->exchange_sig.eddsa_signature);
+}
+
+
+/**
+ * Tests inserting an exchange signing key into the database.
+ *
+ * @param signkey the signing key to insert.
+ * @param expected_result the result we expect the database to return.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey,
+                              enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_exchange_signkey (plugin->cls,
+                                                          &signkey->master_pub,
+                                                          
&signkey->exchange_pub,
+                                                          signkey->start_date,
+                                                          signkey->expire_date,
+                                                          signkey->end_date,
+                                                          
&signkey->master_sig),
+                         "Insert exchange signkey failed\n");
+  return 0;
+}
+
+
+/**
+ * Tests inserting a deposit into the database.
+ *
+ * @param instance the instance the deposit was made to.
+ * @param signkey the signing key used.
+ * @param deposit the deposit information to insert.
+ * @param expected_result the result we expect the database to return.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_deposit (const struct InstanceData *instance,
+                     const struct ExchangeSignkeyData *signkey,
+                     const struct DepositData *deposit,
+                     enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_deposit (plugin->cls,
+                                                 instance->instance.id,
+                                                 deposit->timestamp,
+                                                 &deposit->h_contract_terms,
+                                                 &deposit->coin_pub,
+                                                 deposit->exchange_url,
+                                                 &deposit->amount_with_fee,
+                                                 &deposit->deposit_fee,
+                                                 &deposit->refund_fee,
+                                                 &deposit->wire_fee,
+                                                 &deposit->h_wire,
+                                                 &deposit->exchange_sig,
+                                                 &signkey->exchange_pub),
+                         "Insert deposit failed\n");
+  return 0;
+}
+
+
+/**
+ * Closure for testing deposit lookup
+ */
+struct TestLookupDeposits_Closure
+{
+  /**
+   * Number of deposits to compare to
+   */
+  unsigned int deposits_to_cmp_length;
+
+  /**
+   * Pointer to array of deposit data
+   */
+  const struct DepositData *deposits_to_cmp;
+
+  /**
+   * Pointer to array of number of matches per deposit
+   */
+  unsigned int *results_matching;
+
+  /**
+   * Total number of results returned
+   */
+  unsigned int results_length;
+};
+
+
+/**
+ * Called after 'test_lookup_deposits'.
+ *
+ * @param cls pointer to the test lookup closure.
+ * @param coin_pub public key of the coin deposited.
+ * @param amount_with_fee amount of the deposit with fees.
+ * @param deposit_fee fee charged for the deposit.
+ * @param refund_fee fee charged in case of a refund.
+ * @param wire_fee fee charged when the money is wired.
+ */
+static void
+lookup_deposits_cb (void *cls,
+                    const char *exchange_url,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const struct TALER_Amount *amount_with_fee,
+                    const struct TALER_Amount *deposit_fee,
+                    const struct TALER_Amount *refund_fee,
+                    const struct TALER_Amount *wire_fee)
+{
+  struct TestLookupDeposits_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i)
+  {
+    if ((GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].amount_with_fee,
+           amount_with_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
+                                amount_with_fee)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].deposit_fee,
+           deposit_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
+                                deposit_fee)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].refund_fee,
+           refund_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee,
+                                refund_fee)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].wire_fee,
+           wire_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee,
+                                wire_fee)))
+    {
+      cmp->results_matching[i] += 1;
+    }
+
+  }
+}
+
+
+/**
+ * Tests looking up deposits from the database.
+ *
+ * @param instance the instance to lookup deposits from.
+ * @param h_contract_terms the contract terms that the deposits should have.
+ * @param deposits_length length of @e deposits.
+ * @param deposits the deposits we expect to be found.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_deposits (const struct InstanceData *instance,
+                      const struct GNUNET_HashCode *h_contract_terms,
+                      unsigned int deposits_length,
+                      const struct DepositData *deposits)
+{
+  unsigned int results_matching[deposits_length];
+  struct TestLookupDeposits_Closure cmp = {
+    .deposits_to_cmp_length = deposits_length,
+    .deposits_to_cmp = deposits,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching,
+          0,
+          sizeof (unsigned int) * deposits_length);
+  TEST_COND_RET_ON_FAIL (0 <=
+                         plugin->lookup_deposits (plugin->cls,
+                                                  instance->instance.id,
+                                                  h_contract_terms,
+                                                  &lookup_deposits_cb,
+                                                  &cmp),
+                         "Lookup deposits failed\n");
+  if (deposits_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup deposits failed: incorrect number of results returned 
(%d)\n",
+                cmp.results_length);
+    return 1;
+  }
+  for (unsigned int i = 0; deposits_length > i; ++i)
+  {
+    if (cmp.results_matching[i] != 1)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup deposits failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Called after 'test_lookup_deposits_contract_and_coin'.
+ *
+ * @param cls pointer to the test lookup closure.
+ * @param exchange_url URL to the exchange
+ * @param amount_with_fee amount of the deposit with fees.
+ * @param deposit_fee fee charged for the deposit.
+ * @param refund_fee fee charged in case of a refund.
+ * @param wire_fee fee charged when the money is wired.
+ * @param h_wire hash of the wire transfer details.
+ * @param deposit_timestamp when the deposit was made.
+ * @param refund_deadline deadline for refunding the deposit.
+ * @param exchange_sig signature the exchange made on the deposit.
+ * @param exchange_pub public key of the exchange.
+ */
+static void
+lookup_deposits_contract_coin_cb (void *cls,
+                                  const char *exchange_url,
+                                  const struct TALER_Amount *amount_with_fee,
+                                  const struct TALER_Amount *deposit_fee,
+                                  const struct TALER_Amount *refund_fee,
+                                  const struct TALER_Amount *wire_fee,
+                                  const struct GNUNET_HashCode *h_wire,
+                                  struct GNUNET_TIME_Absolute 
deposit_timestamp,
+                                  struct GNUNET_TIME_Absolute refund_deadline,
+                                  const struct
+                                  TALER_ExchangeSignatureP *exchange_sig,
+                                  const struct
+                                  TALER_ExchangePublicKeyP *exchange_pub)
+{
+  struct TestLookupDeposits_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i)
+  {
+    if ((cmp->deposits_to_cmp[i].timestamp.abs_value_us ==
+         deposit_timestamp.abs_value_us) &&
+        (0 == strcmp (cmp->deposits_to_cmp[i].exchange_url,
+                      exchange_url)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].amount_with_fee,
+           amount_with_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
+                                amount_with_fee)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].deposit_fee,
+           deposit_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
+                                deposit_fee)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].refund_fee,
+           refund_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee,
+                                refund_fee)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].wire_fee,
+           wire_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee,
+                                wire_fee)) &&
+        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire,
+                             h_wire)) &&
+        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].exchange_sig,
+                             exchange_sig)))
+    {
+      cmp->results_matching[i] += 1;
+    }
+  }
+}
+
+
+/**
+ * Tests lookup of deposits by contract and coin.
+ *
+ * @param instance the instance to lookup from.
+ * @param h_contract the contract terms the deposits should have.
+ * @param coin_pub the public key of the coin the deposits should have.
+ * @param deposits_length length of @e deposits.
+ * @param deposits the deposits the db is expected to find.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_deposits_contract_and_coin (const struct InstanceData *instance,
+                                        const struct
+                                        GNUNET_HashCode *h_contract,
+                                        const struct
+                                        TALER_CoinSpendPublicKeyP *coin_pub,
+                                        unsigned int deposits_length,
+                                        const struct DepositData *deposits)
+{
+  unsigned int results_matching[deposits_length];
+  struct TestLookupDeposits_Closure cmp = {
+    .deposits_to_cmp_length = deposits_length,
+    .deposits_to_cmp = deposits,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching,
+          0,
+          sizeof (unsigned int) * deposits_length);
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->lookup_deposits_by_contract_and_coin (
+                           plugin->cls,
+                           instance
+                           ->instance.id,
+                           h_contract,
+                           coin_pub,
+                           &
+                           lookup_deposits_contract_coin_cb,
+                           &cmp),
+                         "Lookup deposits by contract and coin failed\n");
+  if (deposits_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup deposits failed: incorrect number of results 
returned\n");
+    return 1;
+  }
+  for (unsigned int i = 0; deposits_length > i; ++i)
+  {
+    if (cmp.results_matching[i] != 1)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup deposits failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Called after 'test_lookup_deposits_by_order'.
+ *
+ * @param cls pointer to the test lookup closure.
+ * @param deposit_serial row number of the deposit in the database.
+ * @param exchange_url URL to the exchange
+ * @param amount_with_fee amount of the deposit with fees.
+ * @param h_wire hash of the wire transfer details.
+ * @param amount_with_fee amount of the deposit with fees.
+ * @param deposit_fee fee charged for the deposit.
+ * @param coin_pub public key of the coin deposited.
+ */
+static void
+lookup_deposits_order_cb (void *cls,
+                          uint64_t deposit_serial,
+                          const char *exchange_url,
+                          const struct GNUNET_HashCode *h_wire,
+                          const struct TALER_Amount *amount_with_fee,
+                          const struct TALER_Amount *deposit_fee,
+                          const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+  struct TestLookupDeposits_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  for (unsigned int i = 0; i < cmp->deposits_to_cmp_length; ++i)
+  {
+    if ((0 == strcmp (cmp->deposits_to_cmp[i].exchange_url,
+                      exchange_url)) &&
+        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire,
+                             h_wire)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].amount_with_fee,
+           amount_with_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
+                                amount_with_fee)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->deposits_to_cmp[i].deposit_fee,
+           deposit_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
+                                deposit_fee)) &&
+        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].coin_pub,
+                             coin_pub)))
+      cmp->results_matching[i] += 1;
+  }
+}
+
+
+/**
+ * Tests looking up deposits by associated order.
+ *
+ * @param order_serial row number of the order to lookup for.
+ * @param deposits_length length of @e deposits_length.
+ * @param deposits the deposits we expect to be found.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_deposits_by_order (uint64_t order_serial,
+                               unsigned int deposits_length,
+                               const struct DepositData *deposits)
+{
+  unsigned int results_matching[deposits_length];
+  struct TestLookupDeposits_Closure cmp = {
+    .deposits_to_cmp_length = deposits_length,
+    .deposits_to_cmp = deposits,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching,
+          0,
+          sizeof (unsigned int) * deposits_length);
+  if (deposits_length != plugin->lookup_deposits_by_order (plugin->cls,
+                                                           order_serial,
+                                                           &
+                                                           
lookup_deposits_order_cb,
+                                                           &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup deposits by order failed\n");
+    return 1;
+  }
+  if (deposits_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup deposits by order failed: incorrect number of 
results\n");
+    return 1;
+  }
+  for (unsigned int i = 0; i < deposits_length; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup deposits by order failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Container for information for looking up the row number of a deposit.
+ */
+struct LookupDepositSerial_Closure
+{
+  /**
+   * The deposit we're looking for.
+   */
+  const struct DepositData *deposit;
+
+  /**
+   * The serial found.
+   */
+  uint64_t serial;
+};
+
+
+/**
+ * Called after 'get_deposit_serial'.
+ *
+ * @param cls pointer to the test lookup closure.
+ * @param deposit_serial row number of the deposit in the database.
+ * @param exchange_url URL to the exchange
+ * @param amount_with_fee amount of the deposit with fees.
+ * @param h_wire hash of the wire transfer details.
+ * @param amount_with_fee amount of the deposit with fees.
+ * @param deposit_fee fee charged for the deposit.
+ * @param coin_pub public key of the coin deposited.
+ */
+static void
+get_deposit_serial_cb (void *cls,
+                       uint64_t deposit_serial,
+                       const char *exchange_url,
+                       const struct GNUNET_HashCode *h_wire,
+                       const struct TALER_Amount *amount_with_fee,
+                       const struct TALER_Amount *deposit_fee,
+                       const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+  struct LookupDepositSerial_Closure *lookup_cls = cls;
+  if (NULL == lookup_cls)
+    return;
+  if ((0 == strcmp (lookup_cls->deposit->exchange_url,
+                    exchange_url)) &&
+      (0 == GNUNET_memcmp (&lookup_cls->deposit->h_wire,
+                           h_wire)) &&
+      (GNUNET_OK == TALER_amount_cmp_currency (
+         &lookup_cls->deposit->amount_with_fee,
+         amount_with_fee)) &&
+      (0 == TALER_amount_cmp (&lookup_cls->deposit->amount_with_fee,
+                              amount_with_fee)) &&
+      (GNUNET_OK == TALER_amount_cmp_currency (
+         &lookup_cls->deposit->deposit_fee,
+         deposit_fee)) &&
+      (0 == TALER_amount_cmp (&lookup_cls->deposit->deposit_fee,
+                              deposit_fee)) &&
+      (0 == GNUNET_memcmp (&lookup_cls->deposit->coin_pub,
+                           coin_pub)))
+    lookup_cls->serial = deposit_serial;
+}
+
+
+/**
+ * Convenience function to retrive the row number of a deposit in the database.
+ *
+ * @param instance the instance to get deposits from.
+ * @param order the order associated with the deposit.
+ * @param deposit the deposit to lookup the serial for.
+ * @return the row number of the deposit.
+ */
+static uint64_t
+get_deposit_serial (const struct InstanceData *instance,
+                    const struct OrderData *order,
+                    const struct DepositData *deposit)
+{
+  uint64_t order_serial = get_order_serial (instance,
+                                            order);
+  struct LookupDepositSerial_Closure lookup_cls = {
+    .deposit = deposit,
+    .serial = 0
+  };
+
+  GNUNET_assert (0 <
+                 plugin->lookup_deposits_by_order (plugin->cls,
+                                                   order_serial,
+                                                   &get_deposit_serial_cb,
+                                                   &lookup_cls));
+  GNUNET_assert (0 != lookup_cls.serial);
+
+  return lookup_cls.serial;
+}
+
+
+/**
+ * Closure for deposit tests.
+ */
+struct TestDeposits_Closure
+{
+  /**
+   * The instance settings
+   */
+  struct InstanceData instance;
+
+  /**
+   * The merchant account
+   */
+  struct TALER_MERCHANTDB_AccountDetails account;
+
+  /**
+   * The exchange signing key
+   */
+  struct ExchangeSignkeyData signkey;
+
+  /**
+   * The order data
+   */
+  struct OrderData orders[2];
+
+  /**
+   * The array of deposits
+   */
+  struct DepositData deposits[3];
+};
+
+
+/**
+ * Initializes data for testing deposits.
+ *
+ * @param cls the test closure to initialize.
+ */
+static void
+pre_test_deposits (struct TestDeposits_Closure *cls)
+{
+  /* Instance */
+  make_instance ("test_inst_deposits",
+                 &cls->instance);
+
+  /* Account */
+  make_account (&cls->account);
+
+  /* Signing key */
+  make_exchange_signkey (&cls->signkey);
+
+  /* Order */
+  make_order ("test_deposits_od_1",
+              &cls->orders[0]);
+  make_order ("test_deposits_od_2",
+              &cls->orders[1]);
+
+  /* Deposit */
+  make_deposit (&cls->instance,
+                &cls->account,
+                &cls->orders[0],
+                &cls->signkey,
+                &cls->deposits[0]);
+  make_deposit (&cls->instance,
+                &cls->account,
+                &cls->orders[0],
+                &cls->signkey,
+                &cls->deposits[1]);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:29.00",
+                                         &cls->deposits[1].amount_with_fee));
+  make_deposit (&cls->instance,
+                &cls->account,
+                &cls->orders[1],
+                &cls->signkey,
+                &cls->deposits[2]);
+}
+
+
+/**
+ * Cleans up memory after testing deposits.
+ *
+ * @param cls the closure containing memory to free.
+ */
+static void
+post_test_deposits (struct TestDeposits_Closure *cls)
+{
+  free_instance_data (&cls->instance);
+  json_decref (cls->orders[0].contract);
+  json_decref (cls->orders[1].contract);
+}
+
+
+/**
+ * Runs tests for deposits.
+ *
+ * @param cls the closure containing test data.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+run_test_deposits (struct TestDeposits_Closure *cls)
+{
+  /* Insert the instance */
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert an account */
+  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
+                                         &cls->account,
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert a signing key */
+  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
+                                                  
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
+                                                  
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Insert an order */
+  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                       &cls->orders[0],
+                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert contract terms */
+  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
+                                                &cls->orders[0],
+                                                
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test inserting a deposit */
+  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                         &cls->signkey,
+                                         &cls->deposits[0],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test double inserts fail */
+  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                         &cls->signkey,
+                                         &cls->deposits[0],
+                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test lookup deposits */
+  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
+                                          &cls->deposits[0].h_contract_terms,
+                                          1,
+                                          cls->deposits));
+  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
+                                          &cls->deposits[2].h_contract_terms,
+                                          0,
+                                          NULL));
+  /* Test lookup deposits by contract and coins */
+  TEST_RET_ON_FAIL (test_lookup_deposits_contract_and_coin (
+                      &cls->instance,
+                      &cls->deposits[0].h_contract_terms,
+                      &cls->deposits[0].coin_pub,
+                      1,
+                      cls->deposits));
+  /* Test multiple deposits */
+  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                         &cls->signkey,
+                                         &cls->deposits[1],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                       &cls->orders[1],
+                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
+                                                &cls->orders[1],
+                                                
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                         &cls->signkey,
+                                         &cls->deposits[2],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
+                                          &cls->deposits[0].h_contract_terms,
+                                          2,
+                                          cls->deposits));
+  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
+                                          &cls->deposits[2].h_contract_terms,
+                                          1,
+                                          &cls->deposits[2]));
+  /* Test lookup deposits by order */
+  {
+    uint64_t order_serial = get_order_serial (&cls->instance,
+                                              &cls->orders[0]);
+    TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
+                                                     2,
+                                                     cls->deposits));
+    order_serial = get_order_serial (&cls->instance,
+                                     &cls->orders[1]);
+    TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
+                                                     1,
+                                                     &cls->deposits[2]));
+  }
+  return 0;
+}
+
+
+/**
+ * Handles functionality for testing deposits.
+ *
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_deposits (void)
+{
+  struct TestDeposits_Closure test_cls;
+  pre_test_deposits (&test_cls);
+  int test_result = run_test_deposits (&test_cls);
+  post_test_deposits (&test_cls);
+  return test_result;
+}
+
+
+/* *********** Transfers ********** */
+
+
+/**
+ * Container for wire fee data for an exchange.
+ */
+struct WireFeeData
+{
+  /**
+   * The method used.
+   */
+  const char *wire_method;
+
+  /**
+   * Hash of the wire method.
+   */
+  struct GNUNET_HashCode h_wire_method;
+
+  /**
+   * Wire fee charged.
+   */
+  struct TALER_Amount wire_fee;
+
+  /**
+   * Closing fee charged.
+   */
+  struct TALER_Amount closing_fee;
+
+  /**
+   * Start date of the wire fee.
+   */
+  struct GNUNET_TIME_Absolute wire_fee_start;
+
+  /**
+   * End date of the wire fee.
+   */
+  struct GNUNET_TIME_Absolute wire_fee_end;
+
+  /**
+   * Signature on the wire fee.
+   */
+  struct TALER_MasterSignatureP fee_sig;
+};
+
+
+/**
+ * Creates data for an exchange wire fee.
+ *
+ * @param signkey the exchange signing key data.
+ * @param wire_fee where to store the wire fee data.
+ */
+static void
+make_wire_fee (const struct ExchangeSignkeyData *signkey,
+               struct WireFeeData *wire_fee)
+{
+  struct TALER_MasterWireFeePS fee_sign = {
+    .purpose = {
+      .size = htonl (sizeof (struct TALER_MasterWireFeePS)),
+      .purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES)
+    }
+
+
+  };
+
+  wire_fee->wire_method = "wire-method";
+  GNUNET_CRYPTO_hash (wire_fee->wire_method,
+                      strlen (wire_fee->wire_method) + 1,
+                      &wire_fee->h_wire_method);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.49",
+                                         &wire_fee->wire_fee));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.49",
+                                         &wire_fee->closing_fee));
+  wire_fee->wire_fee_start = GNUNET_TIME_absolute_get ();
+  wire_fee->wire_fee_end = GNUNET_TIME_absolute_add (wire_fee->wire_fee_start,
+                                                     GNUNET_TIME_UNIT_MONTHS);
+  fee_sign.h_wire_method = wire_fee->h_wire_method;
+  TALER_amount_hton (&fee_sign.wire_fee,
+                     &wire_fee->wire_fee);
+  TALER_amount_hton (&fee_sign.closing_fee,
+                     &wire_fee->closing_fee);
+  fee_sign.start_date = GNUNET_TIME_absolute_hton (wire_fee->wire_fee_start);
+  fee_sign.end_date = GNUNET_TIME_absolute_hton (wire_fee->wire_fee_end);
+  GNUNET_CRYPTO_eddsa_sign (&signkey->master_priv.eddsa_priv,
+                            &fee_sign,
+                            &wire_fee->fee_sig.eddsa_signature);
+}
+
+
+/**
+ * Container for wire transfer data.
+ */
+struct TransferData
+{
+  /**
+   * Id of the transfer.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+
+  /**
+   * The main data for the transfer.
+   */
+  struct TALER_EXCHANGE_TransferData data;
+
+  /**
+   * URL to the exchange the transfer was made through.
+   */
+  const char *exchange_url;
+
+  /**
+   * How much the fee for the deposit was.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Whether the transfer has been confirmed.
+   */
+  bool confirmed;
+
+  /**
+   * Whether the transfer has been verified.
+   */
+  bool verified;
+};
+
+
+/**
+ * Creates a transfer for use with testing.
+ *
+ * @param deposits_length length of @e deposits.
+ * @param deposits list of deposits to combine into one transfer.
+ * @param transfer where to write the transfer data.
+ */
+static void
+make_transfer (const struct ExchangeSignkeyData *signkey,
+               unsigned int deposits_length,
+               const struct DepositData deposits[],
+               struct TransferData *transfer)
+{
+  GNUNET_CRYPTO_seed_weak_random (585);
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &transfer->wtid,
+                              sizeof (struct 
TALER_WireTransferIdentifierRawP));
+  transfer->exchange_url = deposits[0].exchange_url;
+  transfer->verified = false;
+  transfer->confirmed = false;
+
+  struct TALER_TrackTransferDetails *details = NULL;
+
+  transfer->data.details_length = 0;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_get_zero (deposits[0].amount_with_fee.currency,
+                                        &transfer->data.total_amount));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_get_zero (deposits[0].amount_with_fee.currency,
+                                        &transfer->deposit_fee));
+  for (unsigned int i = 0; i < deposits_length; ++i)
+  {
+    GNUNET_array_grow (details,
+                       transfer->data.details_length,
+                       i + 1);
+    details[i].h_contract_terms = deposits[i].h_contract_terms;
+    details[i].coin_pub = deposits[i].coin_pub;
+    details[i].coin_value = deposits[i].amount_with_fee;
+    details[i].coin_fee = deposits[i].deposit_fee;
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&transfer->data.total_amount,
+                                     &transfer->data.total_amount,
+                                     &deposits[i].amount_with_fee));
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&transfer->deposit_fee,
+                                     &transfer->deposit_fee,
+                                     &deposits[i].deposit_fee));
+  }
+  transfer->data.exchange_pub = signkey->exchange_pub;
+  transfer->data.execution_time = GNUNET_TIME_absolute_get ();
+  transfer->data.details = details;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.50",
+                                         &transfer->data.wire_fee));
+}
+
+
+/**
+ * Tests looking up a transfer from the database.
+ *
+ * @param exchange_url url to the exchange of the transfer.
+ * @param wtid id of the transfer.
+ * @param total_expected the total amount of the transfer.
+ * @param fee_expected the fee on the transfer.
+ * @param time_expected when the transfer was made.
+ * @param verified_expected whether the transfer was verified.
+ * @return 1 on success, 0 otherwise.
+ */
+static int
+test_lookup_transfer (const struct TransferData *transfer)
+{
+  struct TALER_Amount total_with_fee;
+  struct TALER_Amount total;
+  struct TALER_Amount fee;
+  struct GNUNET_TIME_Absolute time;
+  bool verified;
+  if (1 != plugin->lookup_transfer (plugin->cls,
+                                    transfer->exchange_url,
+                                    &transfer->wtid,
+                                    &total,
+                                    &fee,
+                                    &time,
+                                    &verified))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer failed\n");
+    return 1;
+  }
+  GNUNET_assert (0 <= TALER_amount_add (&total_with_fee,
+                                        &transfer->data.total_amount,
+                                        &transfer->data.wire_fee));
+  if ((GNUNET_OK != TALER_amount_cmp_currency (&total_with_fee,
+                                               &total)) ||
+      (0 != TALER_amount_cmp (&total_with_fee,
+                              &total)) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (&transfer->data.wire_fee,
+                                               &fee)) ||
+      (0 != TALER_amount_cmp (&transfer->data.wire_fee,
+                              &fee)) ||
+      (transfer->data.execution_time.abs_value_us != time.abs_value_us) ||
+      (transfer->verified != verified))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer failed: mismatched data\n");
+    return 1;
+  }
+  return 0;
+}
+
+
+/**
+ * Closure for testing 'lookup_transfer_summary'
+ */
+struct TestLookupTransferSummary_Closure
+{
+  /**
+   * Id of the order the transfer was made for.
+   */
+  const char *order_id;
+
+  /**
+   * The value of the deposit made.
+   */
+  const struct TALER_Amount *deposit_value;
+
+  /**
+   * The fee on the deposit made.
+   */
+  const struct TALER_Amount *deposit_fee;
+
+  /**
+   * 0 if the comparison is true, 1 if false.
+   */
+  int result;
+};
+
+
+/**
+ * Called after 'test_lookup_transfer_summary'.
+ *
+ * @param cls pointer to 'TestLookupTransferSummary_Closure'.
+ * @param order_id id of the order the transfer was made for.
+ * @param deposit_value the value of the deposit made.
+ * @param deposit_fee the fee on the deposit made.
+ */
+static void
+lookup_transfer_summary_cb (void *cls,
+                            const char *order_id,
+                            const struct TALER_Amount *deposit_value,
+                            const struct TALER_Amount *deposit_fee)
+{
+  struct TestLookupTransferSummary_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  if ((0 == strcmp (cmp->order_id,
+                    order_id)) &&
+      (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_value,
+                                               deposit_value)) &&
+      (0 == TALER_amount_cmp (cmp->deposit_value,
+                              deposit_value)) &&
+      (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_fee,
+                                               deposit_fee)) &&
+      (0 == TALER_amount_cmp (cmp->deposit_fee,
+                              deposit_fee)))
+    cmp->result = 0;
+  else
+    cmp->result = 1;
+}
+
+
+/**
+ * Tests looking up a transfer's summary.
+ *
+ * @param exchange_url url to the exchange for the transfer.
+ * @param wtid identifier of the transfer.
+ * @param expected_order_id the id of the order associated with the transfer.
+ * @param expected_deposit_value the amount of the deposit made for the 
transfer.
+ * @param expected_deposit_fee the fee on the deposit made for the transfer.
+ * @return 1 on success, 0 otherwise.
+ */
+static int
+test_lookup_transfer_summary (const char *exchange_url,
+                              const struct
+                              TALER_WireTransferIdentifierRawP *wtid,
+                              const char *expected_order_id,
+                              const struct TALER_Amount 
*expected_deposit_value,
+                              const struct TALER_Amount *expected_deposit_fee)
+{
+  struct TestLookupTransferSummary_Closure cmp = {
+    .order_id = expected_order_id,
+    .deposit_value = expected_deposit_value,
+    .deposit_fee = expected_deposit_fee,
+    .result = 0
+  };
+  if (1 != plugin->lookup_transfer_summary (plugin->cls,
+                                            exchange_url,
+                                            wtid,
+                                            &lookup_transfer_summary_cb,
+                                            &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer summary failed\n");
+    return 1;
+  }
+  if (0 != cmp.result)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer summary failed: mismatched data\n");
+    return 1;
+  }
+  return 0;
+}
+
+
+/**
+ * Closure for testing 'lookup_transfer_details'.
+ */
+struct TestLookupTransferDetails_Closure
+{
+  /**
+   * Length of @e details_to_cmp.
+   */
+  unsigned int details_to_cmp_length;
+
+  /**
+   * The details we expect to find.
+   */
+  const struct TALER_TrackTransferDetails *details_to_cmp;
+
+  /**
+   * Number of results matching each detail in @e details_to_cmp.
+   */
+  unsigned int *results_matching;
+
+  /**
+   * Total number of results found.
+   */
+  unsigned int results_length;
+};
+
+
+/**
+ * Called after 'test_lookup_transfer_details'.
+ *
+ * @param cls pointer to 'TestLookupTransferDetails_Closure'.
+ * @param current_offset offset within transfer details.
+ * @param details the details that were found.
+ */
+static void
+lookup_transfer_details_cb (void *cls,
+                            unsigned int current_offset,
+                            const struct TALER_TrackTransferDetails *details)
+{
+  struct TestLookupTransferDetails_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  for (unsigned int i = 0; cmp->details_to_cmp_length > i; ++i)
+  {
+    if ((0 == GNUNET_memcmp (&cmp->details_to_cmp[i].h_contract_terms,
+                             &details->h_contract_terms)) &&
+        (0 == GNUNET_memcmp (&cmp->details_to_cmp[i].coin_pub,
+                             &details->coin_pub)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->details_to_cmp[i].coin_value,
+           &details->coin_value)) &&
+        (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_value,
+                                &details->coin_value)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->details_to_cmp[i].coin_fee,
+           &details->coin_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_fee,
+                                &details->coin_fee)))
+    {
+      cmp->results_matching[i] += 1;
+    }
+  }
+  cmp->results_length += 1;
+}
+
+
+/**
+ * Tests looking up details for a wire transfer.
+ *
+ * @param exchange_url url to the exchange.
+ * @param wtid id of the transfer.
+ * @param details_length the length of @e details.
+ * @param details the details we expect to be returned.
+ * @return 1 on success, 0 otherwise.
+ */
+static int
+test_lookup_transfer_details (const char *exchange_url,
+                              const struct
+                              TALER_WireTransferIdentifierRawP *wtid,
+                              unsigned int details_length,
+                              const struct TALER_TrackTransferDetails *details)
+{
+  unsigned int results_matching[details_length];
+  struct TestLookupTransferDetails_Closure cmp = {
+    .details_to_cmp_length = details_length,
+    .details_to_cmp = details,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching,
+          0,
+          sizeof (unsigned int) * details_length);
+  if (1 != plugin->lookup_transfer_details (plugin->cls,
+                                            exchange_url,
+                                            wtid,
+                                            &lookup_transfer_details_cb,
+                                            &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer details failed\n");
+    return 1;
+  }
+  if (details_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer details failed: incorrect number of results 
(%d)\n",
+                cmp.results_length);
+    return 1;
+  }
+  for (unsigned int i = 0; details_length > i; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup transfer details failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Closure for 'lookup_transfer_details_by_order'.
+ */
+struct TestLookupTransferDetailsByOrder_Closure
+{
+  /**
+   * Length of @e transfers_to_cmp.
+   */
+  unsigned int transfers_to_cmp_length;
+
+  /**
+   * List of transfers that we expect to find.
+   */
+  const struct TransferData *transfers_to_cmp;
+
+  /**
+   * How many results match the corresponding element of @e transfers_to_cmp.
+   */
+  unsigned int *results_matching;
+
+  /**
+   * Total number of results found.
+   */
+  unsigned int results_length;
+};
+
+
+/**
+ * Called after 'test_lookup_transfer_details_by_order'.
+ *
+ * @param cls pointer to 'TestLookupTransferDetailsByOrder_Closure'.
+ * @param wtid identifier of the transfer found.
+ * @param exchange_url exchange url of the transfer found.
+ * @param execution_time when the transfer found occured.
+ * @param deposit_value amount of the deposit for the transfer found.
+ * @param deposit_fee amount of the fee for the deposit of the transfer.
+ * @param transfer_confirmed whether the transfer was confirmed.
+ */
+static void
+lookup_transfer_details_order_cb (void *cls,
+                                  const struct
+                                  TALER_WireTransferIdentifierRawP *wtid,
+                                  const char *exchange_url,
+                                  struct GNUNET_TIME_Absolute execution_time,
+                                  const struct TALER_Amount *deposit_value,
+                                  const struct TALER_Amount *deposit_fee,
+                                  bool transfer_confirmed)
+{
+  struct TestLookupTransferDetailsByOrder_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  for (unsigned int i = 0; i < cmp->transfers_to_cmp_length; ++i)
+  {
+    /* Right now lookup_transfer_details_by_order leaves execution_time
+       uninitialized and transfer_confirmed always false. */
+    if ((0 == GNUNET_memcmp (&cmp->transfers_to_cmp[i].wtid,
+                             wtid)) &&
+        (0 == strcmp (cmp->transfers_to_cmp[i].exchange_url,
+                      exchange_url)) &&
+        /*(cmp->transfers_to_cmp[i].execution_time.abs_value_us ==
+         execution_time.abs_value_us) &&*/
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->transfers_to_cmp[i].data.total_amount,
+           deposit_value)) &&
+        (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
+                                deposit_value)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->transfers_to_cmp[i].deposit_fee,
+           deposit_fee)) &&
+        (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].deposit_fee,
+                                deposit_fee)) /* &&
+        (cmp->transfers_to_cmp[i].confirmed == transfer_confirmed)*/)
+      cmp->results_matching[i] += 1;
+  }
+}
+
+
+/**
+ * Tests looking up wire transfers associated with an order.
+ *
+ * @param order_serial the order to be queried.
+ * @param transfers_length length of @e transfers.
+ * @param transfers the transfers we expect to be found.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_transfer_details_by_order (uint64_t order_serial,
+                                       unsigned int transfers_length,
+                                       const struct
+                                       TransferData *transfers)
+{
+  unsigned int results_matching[transfers_length];
+  struct TestLookupTransferDetailsByOrder_Closure cmp = {
+    .transfers_to_cmp_length = transfers_length,
+    .transfers_to_cmp = transfers,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching,
+          0,
+          sizeof (unsigned int) * transfers_length);
+  if (transfers_length != plugin->lookup_transfer_details_by_order 
(plugin->cls,
+                                                                    
order_serial,
+                                                                    &
+                                                                    
lookup_transfer_details_order_cb,
+                                                                    &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer details by order failed\n");
+    return 1;
+  }
+  if (transfers_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfer details by order failed: incorrect number of 
results\n");
+    return 1;
+  }
+  for (unsigned int i = 0; i < transfers_length; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup transfer details by order failed: mismatched 
data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Tests inserting wire fee data for an exchange.
+ *
+ * @param signkey the signing key for the exchange.
+ * @param wire_fee the wire fee data.
+ * @param expected_result what the database should return.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_wire_fee (const struct ExchangeSignkeyData *signkey,
+                      const struct WireFeeData *wire_fee,
+                      enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->store_wire_fee_by_exchange (plugin->cls,
+                                                             &signkey->
+                                                             master_pub,
+                                                             &wire_fee->
+                                                             h_wire_method,
+                                                             
&wire_fee->wire_fee,
+                                                             &wire_fee->
+                                                             closing_fee,
+                                                             wire_fee->
+                                                             wire_fee_start,
+                                                             wire_fee->
+                                                             wire_fee_end,
+                                                             
&wire_fee->fee_sig),
+                         "Store wire fee by exchange failed\n");
+  return 0;
+}
+
+
+/**
+ * Tests looking up wire fee data for an exchange.
+ *
+ * @param signkey the signing key to use for lookup.
+ * @param wire_fee_data the wire fee data we expect to find.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_wire_fee (const struct ExchangeSignkeyData *signkey,
+                      const struct WireFeeData *wire_fee_data)
+{
+  struct TALER_Amount wire_fee;
+  struct TALER_Amount closing_fee;
+  struct GNUNET_TIME_Absolute start_date;
+  struct GNUNET_TIME_Absolute end_date;
+  struct TALER_MasterSignatureP master_sig;
+  if (1 != plugin->lookup_wire_fee (plugin->cls,
+                                    &signkey->master_pub,
+                                    wire_fee_data->wire_method,
+                                    GNUNET_TIME_absolute_get (),
+                                    &wire_fee,
+                                    &closing_fee,
+                                    &start_date,
+                                    &end_date,
+                                    &master_sig))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup wire fee failed\n");
+    return 1;
+  }
+  if ((GNUNET_OK != TALER_amount_cmp_currency (&wire_fee_data->wire_fee,
+                                               &wire_fee)) ||
+      (0 != TALER_amount_cmp (&wire_fee_data->wire_fee,
+                              &wire_fee)) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (&wire_fee_data->closing_fee,
+                                               &closing_fee)) ||
+      (0 != TALER_amount_cmp (&wire_fee_data->closing_fee,
+                              &closing_fee)) ||
+      (wire_fee_data->wire_fee_start.abs_value_us != start_date.abs_value_us) 
||
+      (wire_fee_data->wire_fee_end.abs_value_us != end_date.abs_value_us) ||
+      (0 != GNUNET_memcmp (&wire_fee_data->fee_sig,
+                           &master_sig)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup wire fee failed: mismatched data\n");
+    return 1;
+  }
+  return 0;
+}
+
+
+/**
+ * Closure for 'lookup_transfers'.
+ */
+struct TestLookupTransfers_Closure
+{
+  /**
+   * Length of @e transfers_to_cmp.
+   */
+  unsigned int transfers_to_cmp_length;
+
+  /**
+   * The transfers we expect to find.
+   */
+  const struct TransferData *transfers_to_cmp;
+
+  /**
+   * Number of results matching each transfer.
+   */
+  unsigned int *results_matching;
+
+  /**
+   * Total number of results found.
+   */
+  unsigned int results_length;
+};
+
+
+/**
+ * Function called after 'test_lookup_transfers'.
+ *
+ * @param cls pointer to 'TestLookupTransfers_Closure'.
+ * @param credit_amount how much was wired to the merchant (minus fees)
+ * @param wtid wire transfer identifier
+ * @param payto_uri target account that received the wire transfer
+ * @param exchange_url base URL of the exchange that made the wire transfer
+ * @param transfer_serial_id serial number identifying the transfer in the 
backend
+ * @param execution_time when did the exchange make the transfer, 
#GNUNET_TIME_UNIT_FOREVER_ABS
+ *           if it did not yet happen
+ * @param verified true if we checked the exchange's answer and liked it,
+ *                 false there is a problem (verification failed or did not 
yet happen)
+ * @param confirmed true if the merchant confirmed this wire transfer
+ *                 false if it is so far only claimed to have been made by the 
exchange
+ */
+static void
+lookup_transfers_cb (void *cls,
+                     const struct TALER_Amount *credit_amount,
+                     const struct TALER_WireTransferIdentifierRawP *wtid,
+                     const char *payto_uri,
+                     const char *exchange_url,
+                     uint64_t transfer_serial_id,
+                     struct GNUNET_TIME_Absolute execution_time,
+                     bool verified,
+                     bool confirmed)
+{
+  struct TestLookupTransfers_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  for (unsigned int i = 0; cmp->transfers_to_cmp_length > i; ++i)
+  {
+    if ((GNUNET_OK ==
+         TALER_amount_cmp_currency (
+           &cmp->transfers_to_cmp[i].data.total_amount,
+           credit_amount)) &&
+        (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
+                                credit_amount)) &&
+        (cmp->transfers_to_cmp[i].data.execution_time.abs_value_us ==
+         execution_time.abs_value_us))
+    {
+      cmp->results_matching[i] += 1;
+    }
+  }
+  cmp->results_length += 1;
+}
+
+
+/**
+ * Tests looking up transfers from the database.
+ *
+ * @param instance the instance to lookup from.
+ * @param account the account the transfer was made to.
+ * @param before do not return transfers before this time.
+ * @param after do not return transfers after this time.
+ * @param limit maximum number of transfers to return.
+ * @param offset row in the database to start with.
+ * @param filter_verified how to filter verified transfers.
+ * @param transfers_length length of @e transfers.
+ * @param transfers the transfers we expect to find.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_lookup_transfers (const struct InstanceData *instance,
+                       const struct TALER_MERCHANTDB_AccountDetails *account,
+                       struct GNUNET_TIME_Absolute before,
+                       struct GNUNET_TIME_Absolute after,
+                       int64_t limit,
+                       uint64_t offset,
+                       enum TALER_EXCHANGE_YesNoAll filter_verified,
+                       unsigned int transfers_length,
+                       const struct TransferData *transfers)
+{
+  unsigned int results_matching[transfers_length];
+  struct TestLookupTransfers_Closure cmp = {
+    .transfers_to_cmp_length = transfers_length,
+    .transfers_to_cmp = transfers,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching,
+          0,
+          sizeof (unsigned int) * transfers_length);
+  if (1 != plugin->lookup_transfers (plugin->cls,
+                                     instance->instance.id,
+                                     account->payto_uri,
+                                     before,
+                                     after,
+                                     limit,
+                                     offset,
+                                     filter_verified,
+                                     &lookup_transfers_cb,
+                                     &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfers failed\n");
+    return 1;
+  }
+  if (transfers_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup transfers failed: incorrect number of results\n");
+    return 1;
+  }
+  for (unsigned int i = 0; transfers_length > i; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup transfers failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Tests inserting a transfer into the database.
+ *
+ * @param instance the instance to use.
+ * @param account the account to transfer to.
+ * @param transfer the transfer to insert.
+ * @param expected_result the result we expect the db to return.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_transfer (const struct InstanceData *instance,
+                      const struct TALER_MERCHANTDB_AccountDetails *account,
+                      const struct TransferData *transfer,
+                      enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_transfer (plugin->cls,
+                                                  instance->instance.id,
+                                                  transfer->exchange_url,
+                                                  &transfer->wtid,
+                                                  &transfer->data.total_amount,
+                                                  account->payto_uri,
+                                                  transfer->confirmed),
+                         "Insert transfer failed\n");
+  return 0;
+}
+
+
+/**
+ * Tests linking a deposit to a transfer.
+ *
+ * @param instance the instance that the deposit and transfer are for.
+ * @param signkey the signing used on the deposit.
+ * @param order the order the deposit and transfer were made for.
+ * @param transfer the transfer.
+ * @param expected_result the result the database should return.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_deposit_to_transfer (const struct InstanceData *instance,
+                                 const struct ExchangeSignkeyData *signkey,
+                                 const struct OrderData *order,
+                                 const struct DepositData *deposit,
+                                 const struct TransferData *transfer,
+                                 enum GNUNET_DB_QueryStatus expected_result)
+{
+  const struct TALER_EXCHANGE_DepositData deposit_data = {
+    .exchange_pub = signkey->exchange_pub,
+    .exchange_sig = deposit->exchange_sig,
+    .wtid = transfer->wtid,
+    .execution_time = transfer->data.execution_time,
+    .coin_contribution = deposit->amount_with_fee
+  };
+  uint64_t deposit_serial = get_deposit_serial (instance,
+                                                order,
+                                                deposit);
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_deposit_to_transfer (plugin->cls,
+                                                             deposit_serial,
+                                                             &deposit_data),
+                         "insert deposit to transfer failed\n");
+  return 0;
+}
+
+
+/**
+ * Inserts details for a transfer into the database.
+ *
+ * @param instance the instance the transfer is in.
+ * @param account the destination account for the transfer.
+ * @param transfer the transfer we are adding details to.
+ * @param expected_result the result expected from the db.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_insert_transfer_details (const struct InstanceData *instance,
+                              const struct
+                              TALER_MERCHANTDB_AccountDetails *account,
+                              const struct TransferData *transfer,
+                              enum GNUNET_DB_QueryStatus expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_transfer_details (plugin->cls,
+                                                          
instance->instance.id,
+                                                          
transfer->exchange_url,
+                                                          account->payto_uri,
+                                                          &transfer->wtid,
+                                                          &transfer->data),
+                         "Insert transfer details failed\n");
+  return 0;
+}
+
+
+/**
+ * Container for data used when testing transfers.
+ */
+struct TestTransfers_Closure
+{
+  /**
+   * The instance.
+   */
+  struct InstanceData instance;
+
+  /**
+   * The account.
+   */
+  struct TALER_MERCHANTDB_AccountDetails account;
+
+  /**
+   * The exchange signing key.
+   */
+  struct ExchangeSignkeyData signkey;
+
+  /**
+   * The order data.
+   */
+  struct OrderData order;
+
+  /**
+   * The deposit data.
+   */
+  struct DepositData deposit;
+
+  /**
+   * Wire fee data.
+   */
+  struct WireFeeData wire_fee[2];
+
+  /**
+   * The transfers.
+   */
+  struct TransferData transfers[1];
+};
+
+
+/**
+ * Prepares for testing transfers.
+ *
+ * @param cls the test data.
+ */
+static void
+pre_test_transfers (struct TestTransfers_Closure *cls)
+{
+  /* Instance */
+  make_instance ("test_inst_transfers",
+                 &cls->instance);
+
+  /* Account */
+  make_account (&cls->account);
+
+  /* Order */
+  make_order ("test_transfers_od_1",
+              &cls->order);
+
+  /* Signing key */
+  make_exchange_signkey (&cls->signkey);
+
+  /* Deposit */
+  make_deposit (&cls->instance,
+                &cls->account,
+                &cls->order,
+                &cls->signkey,
+                &cls->deposit);
+
+  /* Wire fee */
+  make_wire_fee (&cls->signkey,
+                 &cls->wire_fee[0]);
+  make_wire_fee (&cls->signkey,
+                 &cls->wire_fee[1]);
+  cls->wire_fee[1].wire_method = "wire-method-2";
+  GNUNET_CRYPTO_hash (cls->wire_fee[1].wire_method,
+                      strlen (cls->wire_fee[1].wire_method) + 1,
+                      &cls->wire_fee[1].h_wire_method);
+
+  /* Transfers */
+  make_transfer (&cls->signkey,
+                 1,
+                 &cls->deposit,
+                 &cls->transfers[0]);
+  cls->transfers[0].confirmed = true;
+}
+
+
+/**
+ * Cleans up after testing transfers.
+ *
+ * @param cls the test data.
+ */
+static void
+post_test_transfers (struct TestTransfers_Closure *cls)
+{
+  free_instance_data (&cls->instance);
+  free_order_data (&cls->order);
+}
+
+
+/**
+ * Runs the tests for transfers.
+ *
+ * @param cls the test data.
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+run_test_transfers (struct TestTransfers_Closure *cls)
+{
+  uint64_t order_serial;
+
+  /* Test lookup wire fee fails when it isn't in the db */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+                         plugin->lookup_wire_fee (plugin->cls,
+                                                  &cls->signkey.master_pub,
+                                                  cls->wire_fee[0].wire_method,
+                                                  GNUNET_TIME_absolute_get (),
+                                                  NULL,
+                                                  NULL,
+                                                  NULL,
+                                                  NULL,
+                                                  NULL),
+                         "Lookup wire fee failed\n");
+  /* Test store wire fee by exchange */
+  TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
+                                          &cls->wire_fee[0],
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test double insertion fails */
+  TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
+                                          &cls->wire_fee[0],
+                                          
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  /* Test lookup wire fee by exchange */
+  TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
+                                          &cls->wire_fee[0]));
+  /* Test different wire fees for different methods. */
+  TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
+                                          &cls->wire_fee[1],
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
+                                          &cls->wire_fee[1]));
+  /* Insert the instance */
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert the account */
+  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
+                                         &cls->account,
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert a signing key */
+  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
+                                                  
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert an order */
+  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                       &cls->order,
+                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert contract terms */
+  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
+                                                &cls->order,
+                                                
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  order_serial = get_order_serial (&cls->instance,
+                                   &cls->order);
+  /* Insert the deposit */
+  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                         &cls->signkey,
+                                         &cls->deposit,
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert the transfer */
+  TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
+                                          &cls->account,
+                                          &cls->transfers[0],
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
+                                          &cls->account,
+                                          &cls->transfers[0],
+                                          
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
+                                                     &cls->signkey,
+                                                     &cls->order,
+                                                     &cls->deposit,
+                                                     &cls->transfers[0],
+                                                     
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
+                                                     &cls->signkey,
+                                                     &cls->order,
+                                                     &cls->deposit,
+                                                     &cls->transfers[0],
+                                                     
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+  TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance,
+                                                  &cls->account,
+                                                  &cls->transfers[0],
+                                                  
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_lookup_payment_status (order_serial,
+                                                NULL,
+                                                false,
+                                                true));
+  TEST_RET_ON_FAIL (test_lookup_transfer (&cls->transfers[0]));
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->set_transfer_status_to_verified (plugin->cls,
+                                                                  cls->deposit.
+                                                                  exchange_url,
+                                                                  &cls->
+                                                                  transfers[0].
+                                                                  wtid),
+                         "Set transfer status to verified failed\n");
+  cls->transfers[0].verified = true;
+  TEST_RET_ON_FAIL (test_lookup_transfer (&cls->transfers[0]));
+  TEST_RET_ON_FAIL (test_lookup_transfer_summary (cls->deposit.exchange_url,
+                                                  &cls->transfers[0].wtid,
+                                                  cls->order.id,
+                                                  
&cls->deposit.amount_with_fee,
+                                                  &cls->deposit.deposit_fee));
+  TEST_RET_ON_FAIL (test_lookup_transfer_details (cls->deposit.exchange_url,
+                                                  &cls->transfers[0].wtid,
+                                                  1,
+                                                  &cls->transfers[0].data.
+                                                  details[0]));
+  TEST_RET_ON_FAIL (test_lookup_transfer_details_by_order (order_serial,
+                                                           1,
+                                                           
&cls->transfers[0]));
+  TEST_RET_ON_FAIL (test_lookup_transfers (&cls->instance,
+                                           &cls->account,
+                                           GNUNET_TIME_UNIT_FOREVER_ABS,
+                                           GNUNET_TIME_UNIT_ZERO_ABS,
+                                           8,
+                                           0,
+                                           TALER_EXCHANGE_YNA_ALL,
+                                           1,
+                                           &cls->transfers[0]));
+  return 0;
+}
+
+
+/**
+ * Takes care of all work for testing transfers.
+ *
+ * @return 0 on success, 1 otherwise.
+ */
+static int
+test_transfers (void)
+{
+  struct TestTransfers_Closure test_cls;
+  pre_test_transfers (&test_cls);
+  int test_result = run_test_transfers (&test_cls);
+  post_test_transfers (&test_cls);
+  return test_result;
+}
+
+
+/* Reserves and tips */
+
+
+struct ReserveData
+{
+  /**
+   * The reserve public key
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * The reserve private key
+   */
+  struct TALER_ReservePrivateKeyP reserve_priv;
+
+  /**
+   * The reserve initial amount
+   */
+  struct TALER_Amount initial_amount;
+
+  /**
+   * The exchange url
+   */
+  const char *exchange_url;
+
+  /**
+   * The expiration date
+   */
+  struct GNUNET_TIME_Absolute expiration;
+};
+
+
+static int
+test_insert_reserve (const struct InstanceData *instance,
+                     const struct ReserveData *reserve,
+                     enum TALER_ErrorCode expected_result)
+{
+  TEST_COND_RET_ON_FAIL (expected_result ==
+                         plugin->insert_reserve (plugin->cls,
+                                                 instance->instance.id,
+                                                 &reserve->reserve_priv,
+                                                 &reserve->reserve_pub,
+                                                 reserve->exchange_url,
+                                                 &reserve->initial_amount,
+                                                 reserve->expiration),
+                         "Insert reserve failed\n");
+  return 0;
+}
+
+
+struct TestLookupReserve_Closure
+{
+  const struct ReserveData *reserve_to_cmp;
+
+  unsigned int tips_length;
+
+  const struct TALER_MERCHANTDB_TipDetails *tips;
+
+  int result_matches;
+};
+
+
+static void
+lookup_reserve_cb (void *cls,
+                   struct GNUNET_TIME_Absolute creation_time,
+                   struct GNUNET_TIME_Absolute expiration_time,
+                   const struct TALER_Amount *merchant_initial_amount,
+                   const struct TALER_Amount *exchange_initial_amount,
+                   const struct TALER_Amount *picked_up_amount,
+                   const struct TALER_Amount *committed_amount,
+                   bool active,
+                   unsigned int tips_length,
+                   const struct TALER_MERCHANTDB_TipDetails *tips)
+{
+  struct TestLookupReserve_Closure *cmp = cls;
+  unsigned int tip_cmp_results[tips_length];
+  if (NULL == cmp)
+    return;
+  if ((cmp->reserve_to_cmp->expiration.abs_value_us !=
+       expiration_time.abs_value_us) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (
+         &cmp->reserve_to_cmp->initial_amount,
+         merchant_initial_amount)) ||
+      (0 != TALER_amount_cmp (&cmp->reserve_to_cmp->initial_amount,
+                              merchant_initial_amount)) ||
+      (cmp->tips_length != tips_length))
+  {
+    cmp->result_matches = 1;
+    return;
+  }
+  memset (tip_cmp_results, 0, sizeof (unsigned int) * tips_length);
+  for (unsigned int i = 0; tips_length > i; ++i)
+  {
+    for (unsigned int j = 0; tips_length > j; ++j)
+    {
+      if ((GNUNET_OK == TALER_amount_cmp_currency (&cmp->tips[i].total_amount,
+                                                   &tips[j].total_amount)) &&
+          (0 == TALER_amount_cmp (&cmp->tips[i].total_amount,
+                                  &tips[j].total_amount)) &&
+          (0 == strcmp (cmp->tips[i].reason,
+                        tips[j].reason)))
+      {
+        tip_cmp_results[i] += 1;
+      }
+    }
+  }
+  for (unsigned int i = 0; tips_length > i; ++i)
+  {
+    if (1 != tip_cmp_results[i])
+    {
+      cmp->result_matches = 1;
+      return;
+    }
+  }
+  cmp->result_matches = 0;
+}
+
+
+static int
+test_lookup_reserve (const char *instance_id,
+                     const struct TALER_ReservePublicKeyP *reserve_pub,
+                     const struct ReserveData *reserve)
+{
+  struct TestLookupReserve_Closure cmp = {
+    .reserve_to_cmp = reserve,
+    .tips_length = 0,
+    .tips = NULL,
+    .result_matches = 0
+  };
+  if (1 != plugin->lookup_reserve (plugin->cls,
+                                   instance_id,
+                                   reserve_pub,
+                                   false,
+                                   &lookup_reserve_cb,
+                                   &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup reserve failed\n");
+    return 1;
+  }
+  if (0 != cmp.result_matches)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup reserve failed: result does not match\n");
+    return 1;
+  }
+  return 0;
+}
+
+
+struct TestLookupReserves_Closure
+{
+  unsigned int reserves_to_cmp_length;
+
+  const struct ReserveData *reserves_to_cmp;
+
+  unsigned int *results_matching;
+
+  unsigned int results_length;
+};
+
+
+static void
+lookup_reserves_cb (void *cls,
+                    const struct TALER_ReservePublicKeyP *reserve_pub,
+                    struct GNUNET_TIME_Absolute creation_time,
+                    struct GNUNET_TIME_Absolute expiration_time,
+                    const struct TALER_Amount *merchant_initial_amount,
+                    const struct TALER_Amount *exchange_initial_amount,
+                    const struct TALER_Amount *pickup_amount,
+                    const struct TALER_Amount *committed_amount,
+                    bool active)
+{
+  struct TestLookupReserves_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  for (unsigned int i = 0; cmp->reserves_to_cmp_length > i; ++i)
+  {
+    if ((0 == GNUNET_memcmp (&cmp->reserves_to_cmp[i].reserve_pub,
+                             reserve_pub)) &&
+        (cmp->reserves_to_cmp[i].expiration.abs_value_us ==
+         expiration_time.abs_value_us) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->reserves_to_cmp[i].initial_amount,
+           merchant_initial_amount)) &&
+        (0 == TALER_amount_cmp (&cmp->reserves_to_cmp[i].initial_amount,
+                                merchant_initial_amount)))
+    {
+      cmp->results_matching[i] += 1;
+    }
+  }
+  cmp->results_length += 1;
+}
+
+
+static int
+test_lookup_reserves (const char *instance_id,
+                      unsigned int reserves_length,
+                      const struct ReserveData *reserves)
+{
+  unsigned int results_matching[reserves_length];
+  struct TestLookupReserves_Closure cmp = {
+    .reserves_to_cmp_length = reserves_length,
+    .reserves_to_cmp = reserves,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching, 0, sizeof (unsigned int) * reserves_length);
+  if (1 != plugin->lookup_reserves (plugin->cls,
+                                    instance_id,
+                                    GNUNET_TIME_absolute_get_zero_ (),
+                                    TALER_EXCHANGE_YNA_ALL,
+                                    TALER_EXCHANGE_YNA_ALL,
+                                    &lookup_reserves_cb,
+                                    &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup reserves failed\n");
+    return 1;
+  }
+  if (reserves_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup reserves failed: incorrect number of results (%d)\n",
+                cmp.results_length);
+    return 1;
+  }
+  for (unsigned int i = 0; reserves_length > i; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup reserves failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+static void
+lookup_pending_reserves_cb (void *cls,
+                            const char *instance_id,
+                            const char *exchange_url,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            const struct TALER_Amount *expected_amount)
+{
+  struct TestLookupReserves_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  for (unsigned int i = 0; cmp->reserves_to_cmp_length > i; ++i)
+  {
+    if ((0 == GNUNET_memcmp (&cmp->reserves_to_cmp[i].reserve_pub,
+                             reserve_pub)) &&
+        (0 == strcmp (cmp->reserves_to_cmp[i].exchange_url,
+                      exchange_url)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->reserves_to_cmp[i].initial_amount,
+           expected_amount)) &&
+        (0 == TALER_amount_cmp (&cmp->reserves_to_cmp[i].initial_amount,
+                                expected_amount)))
+    {
+      cmp->results_matching[i] += 1;
+    }
+  }
+  cmp->results_length += 1;
+}
+
+
+static int
+test_lookup_pending_reserves (unsigned int reserves_length,
+                              const struct ReserveData *reserves)
+{
+  unsigned int results_matching[reserves_length];
+  struct TestLookupReserves_Closure cmp = {
+    .reserves_to_cmp_length = reserves_length,
+    .reserves_to_cmp = reserves,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching, 0, sizeof (unsigned int) * reserves_length);
+  if (0 > plugin->lookup_pending_reserves (plugin->cls,
+                                           &lookup_pending_reserves_cb,
+                                           &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup pending reserves failed\n");
+    return 1;
+  }
+  if (reserves_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup pending reserves failed: incorrect number of results 
(%d)\n",
+                cmp.results_length);
+    return 1;
+  }
+  for (unsigned int i = 0; reserves_length > i; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup pending reserves failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+struct TipData
+{
+  struct TALER_MERCHANTDB_TipDetails details;
+  const char *next_url;
+  struct GNUNET_TIME_Absolute expiration;
+};
+
+
+static void
+make_tip (struct TipData *tip)
+{
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.99",
+                                         &tip->details.total_amount));
+  tip->details.reason = "because";
+  tip->next_url = "https://taler.net";;
+}
+
+
+static int
+test_authorize_tip (const struct InstanceData *instance,
+                    const struct ReserveData *reserve,
+                    struct TipData *tip)
+{
+  TEST_COND_RET_ON_FAIL (TALER_EC_NONE ==
+                         plugin->authorize_tip (plugin->cls,
+                                                instance->instance.id,
+                                                &reserve->reserve_pub,
+                                                &tip->details.total_amount,
+                                                tip->details.reason,
+                                                tip->next_url,
+                                                &tip->details.tip_id,
+                                                &tip->expiration),
+                         "Authorize tip failed\n");
+  return 0;
+}
+
+
+static int
+test_lookup_tip (const struct InstanceData *instance,
+                 const struct ReserveData *reserve,
+                 const struct TipData *tip,
+                 const struct TALER_Amount *expected_total_picked_up)
+{
+  struct TALER_Amount total_authorized;
+  struct TALER_Amount total_picked_up;
+  struct GNUNET_TIME_Absolute expiration;
+  char *exchange_url = NULL;
+  struct TALER_ReservePrivateKeyP reserve_priv;
+  if (1 != plugin->lookup_tip (plugin->cls,
+                               instance->instance.id,
+                               &tip->details.tip_id,
+                               &total_authorized,
+                               &total_picked_up,
+                               &expiration,
+                               &exchange_url,
+                               &reserve_priv))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup tip failed\n");
+    if (NULL != exchange_url)
+      GNUNET_free (exchange_url);
+    return 1;
+  }
+  if ((GNUNET_OK != TALER_amount_cmp_currency (&tip->details.total_amount,
+                                               &total_authorized)) ||
+      (0 != TALER_amount_cmp (&tip->details.total_amount,
+                              &total_authorized)) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (expected_total_picked_up,
+                                               &total_picked_up)) ||
+      (0 != TALER_amount_cmp (expected_total_picked_up,
+                              &total_picked_up)) ||
+      (tip->expiration.abs_value_us != expiration.abs_value_us) ||
+      (0 != strcmp (reserve->exchange_url,
+                    exchange_url)) ||
+      (0 != GNUNET_memcmp (&reserve->reserve_priv,
+                           &reserve_priv)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup tip failed: mismatched data\n");
+    if (NULL != exchange_url)
+      GNUNET_free (exchange_url);
+    return 1;
+  }
+  if (NULL != exchange_url)
+    GNUNET_free (exchange_url);
+  return 0;
+}
+
+
+static int
+test_lookup_tip_details (const struct InstanceData *instance,
+                         const struct ReserveData *reserve,
+                         const struct TipData *tip,
+                         const struct TALER_Amount *expected_total_picked_up,
+                         unsigned int expected_pickups_length,
+                         const struct
+                         TALER_MERCHANTDB_PickupDetails *expected_pickups)
+{
+  struct TALER_Amount total_authorized;
+  struct TALER_Amount total_picked_up;
+  char *justification = NULL;
+  struct GNUNET_TIME_Absolute expiration;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  unsigned int pickups_length;
+  struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
+  unsigned int results_matching[expected_pickups_length];
+  if (0 >
+      plugin->lookup_tip_details (plugin->cls,
+                                  instance->instance.id,
+                                  &tip->details.tip_id,
+                                  true,
+                                  &total_authorized,
+                                  &total_picked_up,
+                                  &justification,
+                                  &expiration,
+                                  &reserve_pub,
+                                  &pickups_length,
+                                  &pickups))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup tip details failed\n");
+    if (NULL != justification)
+      GNUNET_free (justification);
+    if (NULL != pickups)
+      GNUNET_free (pickups);
+    return 1;
+  }
+  if ((GNUNET_OK != TALER_amount_cmp_currency (&tip->details.total_amount,
+                                               &total_authorized)) ||
+      (0 != TALER_amount_cmp (&tip->details.total_amount,
+                              &total_authorized)) ||
+      (GNUNET_OK != TALER_amount_cmp_currency (expected_total_picked_up,
+                                               &total_picked_up)) ||
+      (0 != TALER_amount_cmp (expected_total_picked_up,
+                              &total_picked_up)) ||
+      (0 != strcmp (tip->details.reason,
+                    justification)) ||
+      (tip->expiration.abs_value_us != expiration.abs_value_us) ||
+      (0 != GNUNET_memcmp (&reserve->reserve_pub,
+                           &reserve_pub)) ||
+      (expected_pickups_length != pickups_length))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup tip details failed: mismatched data\n");
+    if (NULL != justification)
+      GNUNET_free (justification);
+    if (NULL != pickups)
+      GNUNET_free (pickups);
+    return 1;
+  }
+  memset (results_matching, 0, sizeof (unsigned int) * 
expected_pickups_length);
+  for (unsigned int i = 0; expected_pickups_length > i; ++i)
+  {
+    for (unsigned int j = 0; pickups_length > j; ++j)
+    {
+      /* Compare expected_pickups[i] with pickups[j] */
+      if ((0 == GNUNET_memcmp (&expected_pickups[i].pickup_id,
+                               &pickups[j].pickup_id)) &&
+          (GNUNET_OK == TALER_amount_cmp_currency (
+             &expected_pickups[i].requested_amount,
+             &pickups[j].requested_amount))
+          &&
+          (0 == TALER_amount_cmp (&expected_pickups[i].requested_amount,
+                                  &pickups[j].requested_amount)) &&
+          (expected_pickups[i].num_planchets == pickups[j].num_planchets))
+      {
+        results_matching[i] += 1;
+      }
+    }
+  }
+  for (unsigned int i = 0; expected_pickups_length > i; ++i)
+  {
+    if (1 != results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup tip details failed: mismatched data\n");
+      if (NULL != justification)
+        GNUNET_free (justification);
+      if (NULL != pickups)
+        GNUNET_free (pickups);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+static void
+free_rsa_signature_array (unsigned int sigs_length,
+                          struct GNUNET_CRYPTO_RsaSignature **sigs)
+{
+  for (unsigned int i = 0; sigs_length > i; ++i)
+  {
+    if (NULL != sigs[i])
+      GNUNET_free (sigs[i]);
+  }
+}
+
+
+static int
+test_lookup_pickup (const char *instance_id,
+                    const struct GNUNET_HashCode *tip_id,
+                    const struct GNUNET_HashCode *pickup_id,
+                    const char *expected_exchange_url,
+                    const struct
+                    TALER_ReservePrivateKeyP *expected_reserve_priv,
+                    unsigned int expected_sigs_length,
+                    const struct GNUNET_CRYPTO_RsaSignature **expected_sigs)
+{
+  char *exchange_url = NULL;
+  struct TALER_ReservePrivateKeyP reserve_priv;
+  struct GNUNET_CRYPTO_RsaSignature *sigs[expected_sigs_length];
+  unsigned int results_matching[expected_sigs_length];
+  memset (sigs,
+          0,
+          sizeof (struct GNUNET_CRYPTO_RsaSignature *) * expected_sigs_length);
+  if (0 > plugin->lookup_pickup (plugin->cls,
+                                 instance_id,
+                                 tip_id,
+                                 pickup_id,
+                                 &exchange_url,
+                                 &reserve_priv,
+                                 expected_sigs_length,
+                                 sigs))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup pickup failed\n");
+    if (NULL != exchange_url)
+      GNUNET_free (exchange_url);
+    free_rsa_signature_array (expected_sigs_length,
+                              sigs);
+    return 1;
+  }
+  if ((0 != strcmp (expected_exchange_url,
+                    exchange_url)) ||
+      (0 != GNUNET_memcmp (expected_reserve_priv,
+                           &reserve_priv)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup pickup failed: mismatched data\n");
+    if (NULL != exchange_url)
+      GNUNET_free (exchange_url);
+    free_rsa_signature_array (expected_sigs_length,
+                              sigs);
+    return 1;
+  }
+  memset (results_matching,
+          0,
+          sizeof (unsigned int) * expected_sigs_length);
+  for (unsigned int i = 0; expected_sigs_length > i; ++i)
+  {
+    for (unsigned int j = 0; expected_sigs_length > j; ++j)
+    {
+      /* compare expected_sigs[i] to sigs[j] */
+      if (0 == GNUNET_CRYPTO_rsa_signature_cmp (expected_sigs[i],
+                                                sigs[j]))
+      {
+        results_matching[i] += 1;
+      }
+    }
+  }
+  for (unsigned int i = 0; expected_sigs_length > i; ++i)
+  {
+    if (1 != results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup pickup failed: mismatched data\n");
+      if (NULL != exchange_url)
+        GNUNET_free (exchange_url);
+      free_rsa_signature_array (expected_sigs_length,
+                                sigs);
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+struct TestLookupTips_Closure
+{
+  unsigned int tips_to_cmp_length;
+  const struct TipData *tips_to_cmp;
+  unsigned int results_length;
+  bool *results_match;
+};
+
+
+static void
+lookup_tips_cb (void *cls,
+                uint64_t row_id,
+                struct GNUNET_HashCode tip_id,
+                struct TALER_Amount amount)
+{
+  struct TestLookupTips_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  unsigned int i = cmp->results_length;
+  cmp->results_length += 1;
+  if (cmp->tips_to_cmp_length > i)
+  {
+    if ((0 == GNUNET_memcmp (&cmp->tips_to_cmp[i].details.tip_id,
+                             &tip_id)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->tips_to_cmp[i].details.total_amount,
+           &amount)) &&
+        (0 == TALER_amount_cmp (&cmp->tips_to_cmp[i].details.total_amount,
+                                &amount)))
+      cmp->results_match[i] = true;
+    else
+      cmp->results_match[i] = false;
+  }
+}
+
+
+static int
+test_lookup_tips (const struct InstanceData *instance,
+                  enum TALER_EXCHANGE_YesNoAll expired,
+                  int64_t limit,
+                  uint64_t offset,
+                  unsigned int tips_length,
+                  const struct TipData *tips)
+{
+  bool results_match[tips_length];
+  struct TestLookupTips_Closure cmp = {
+    .tips_to_cmp_length = tips_length,
+    .tips_to_cmp = tips,
+    .results_length = 0,
+    .results_match = results_match
+  };
+
+  memset (results_match,
+          0,
+          sizeof (bool) * tips_length);
+  if (0 > plugin->lookup_tips (plugin->cls,
+                               instance->instance.id,
+                               expired,
+                               limit,
+                               offset,
+                               &lookup_tips_cb,
+                               &cmp))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup tips failed\n");
+    return 1;
+  }
+  if (tips_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup tips failed: incorrect number of results (%d)\n",
+                cmp.results_length);
+    return 1;
+  }
+  for (unsigned int i = 0; i < tips_length; ++i)
+  {
+    if (true != cmp.results_match[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup tips failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Convenience function for testing lookup tips with filters
+ */
+static void
+reverse_tip_data_array (unsigned int tips_length,
+                        struct TipData *tips)
+{
+  struct TipData tmp[tips_length];
+  for (unsigned int i = 0; i < tips_length; ++i)
+    tmp[i] = tips[tips_length - 1 - i];
+  for (unsigned int i = 0; i < tips_length; ++i)
+    tips[i] = tmp[i];
+}
+
+
+struct TestTips_Closure
+{
+  struct InstanceData instance;
+
+  /**
+   * The tip reserve data
+   */
+  struct ReserveData reserve;
+  struct ReserveData expired_reserve;
+
+  /* Tip and pickup data */
+  struct TipData tip;
+  struct TipData bigtip;
+  struct TipData tips[5];
+
+  struct GNUNET_HashCode pickup_id;
+  struct GNUNET_CRYPTO_RsaPrivateKey *pickup_priv;
+  struct GNUNET_CRYPTO_RsaSignature *pickup_sig;
+};
+
+
+static void
+pre_test_tips (struct TestTips_Closure *cls)
+{
+  /* Instance */
+  make_instance ("test_inst_tips",
+                 &cls->instance);
+
+  /* Reserve */
+  GNUNET_CRYPTO_eddsa_key_create (&cls->reserve.reserve_priv.eddsa_priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (&cls->reserve.reserve_priv.eddsa_priv,
+                                      &cls->reserve.reserve_pub.eddsa_pub);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:99.99",
+                                         &cls->reserve.initial_amount));
+  cls->reserve.exchange_url = "exch-url";
+  cls->reserve.expiration =
+    GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+                              GNUNET_TIME_UNIT_WEEKS);
+
+  GNUNET_CRYPTO_eddsa_key_create (
+    &cls->expired_reserve.reserve_priv.eddsa_priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (
+    &cls->expired_reserve.reserve_priv.eddsa_priv,
+    &cls->expired_reserve.reserve_pub.
+    eddsa_pub);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:99.99",
+                                         
&cls->expired_reserve.initial_amount));
+  cls->expired_reserve.exchange_url = "exch-url";
+  cls->expired_reserve.expiration = GNUNET_TIME_UNIT_ZERO_ABS;
+
+  /* Tip/pickup */
+  make_tip (&cls->tip);
+  make_tip (&cls->bigtip);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:99.90",
+                                         &cls->bigtip.details.total_amount));
+  for (unsigned int i = 0; i < 5; ++i)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    char amount[16];
+    make_tip (&cls->tips[i]);
+    GNUNET_snprintf (amount,
+                     16,
+                     "EUR:0.%u0",
+                     i + 1);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount (amount,
+                                           
&cls->tips[i].details.total_amount));
   }
-  if (tip_expiration.abs_value_us != reserve_expiration.abs_value_us)
+
+  cls->pickup_priv = GNUNET_CRYPTO_rsa_private_key_create (2048);
+  cls->pickup_sig = GNUNET_CRYPTO_rsa_sign_fdh (cls->pickup_priv,
+                                                &cls->pickup_id);
+}
+
+
+static void
+post_test_tips (struct TestTips_Closure *cls)
+{
+  free_instance_data (&cls->instance);
+
+  GNUNET_CRYPTO_rsa_private_key_free (cls->pickup_priv);
+  GNUNET_CRYPTO_rsa_signature_free (cls->pickup_sig);
+}
+
+
+static int
+run_test_tips (struct TestTips_Closure *cls)
+{
+  struct TALER_Amount zero;
+  TALER_amount_get_zero ("EUR", &zero);
+
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test insert reserve */
+  TEST_RET_ON_FAIL (test_insert_reserve (&cls->instance,
+                                         &cls->reserve,
+                                         TALER_EC_NONE));
+  /* Test lookup reserve */
+  TEST_RET_ON_FAIL (test_lookup_reserve (cls->instance.instance.id,
+                                         &cls->reserve.reserve_pub,
+                                         &cls->reserve));
+  /* Test lookup pending reserves */
+  TEST_RET_ON_FAIL (test_lookup_pending_reserves (1,
+                                                  &cls->reserve));
+  /* Test reserve activation */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->activate_reserve (plugin->cls,
+                                                   cls->instance.instance.id,
+                                                   &cls->reserve.reserve_pub,
+                                                   
&cls->reserve.initial_amount),
+                         "Activate reserve failed\n");
+  TEST_RET_ON_FAIL (test_lookup_pending_reserves (0,
+                                                  NULL));
+  /* Test inserting a tip */
+  TEST_RET_ON_FAIL (test_authorize_tip (&cls->instance,
+                                        &cls->reserve,
+                                        &cls->tip));
+  /* Test lookup tip */
+  TEST_RET_ON_FAIL (test_lookup_tip (&cls->instance,
+                                     &cls->reserve,
+                                     &cls->tip,
+                                     &zero));
+  /* Test lookup tip details */
+  TEST_RET_ON_FAIL (test_lookup_tip_details (&cls->instance,
+                                             &cls->reserve,
+                                             &cls->tip,
+                                             &zero,
+                                             0,
+                                             NULL));
+  /* Test insert pickup */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->insert_pickup (plugin->cls,
+                                                cls->instance.instance.id,
+                                                &cls->tip.details.tip_id,
+                                                &cls->tip.details.total_amount,
+                                                &cls->pickup_id,
+                                                
&cls->tip.details.total_amount),
+                         "Insert pickup failed\n");
+  /* Test lookup pickup */
+  TEST_RET_ON_FAIL (test_lookup_pickup (cls->instance.instance.id,
+                                        &cls->tip.details.tip_id,
+                                        &cls->pickup_id,
+                                        cls->reserve.exchange_url,
+                                        &cls->reserve.reserve_priv,
+                                        0,
+                                        NULL));
+  /* Test insert pickup blind signature */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->insert_pickup_blind_signature (plugin->cls,
+                                                                
&cls->pickup_id,
+                                                                0,
+                                                                
cls->pickup_sig),
+                         "Insert pickup blind signature failed\n");
+  /* Test that overdrawing the reserve fails */
+  TEST_COND_RET_ON_FAIL (TALER_EC_NONE !=
+                         plugin->authorize_tip (plugin->cls,
+                                                cls->instance.instance.id,
+                                                &cls->reserve.reserve_pub,
+                                                &cls->bigtip.details.
+                                                total_amount,
+                                                cls->bigtip.details.reason,
+                                                cls->bigtip.next_url,
+                                                &cls->bigtip.details.tip_id,
+                                                &cls->reserve.expiration),
+                         "Authorize tip failed\n");
+  /* Test lookup tips */
+  TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
+                                      TALER_EXCHANGE_YNA_ALL,
+                                      1,
+                                      0,
+                                      1,
+                                      &cls->tip));
+  /* Test lookup reserves */
+  TEST_RET_ON_FAIL (test_lookup_reserves (cls->instance.instance.id,
+                                          1,
+                                          &cls->reserve));
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    /* Test lookup tips & friends */
+    struct TipData expected_tips[6];
+    expected_tips[0] = cls->tip;
+    TEST_RET_ON_FAIL (test_insert_reserve (&cls->instance,
+                                           &cls->expired_reserve,
+                                           TALER_EC_NONE));
+    TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                           plugin->activate_reserve (plugin->cls,
+                                                     cls->instance.instance.id,
+                                                     &cls->expired_reserve.
+                                                     reserve_pub,
+                                                     &cls->expired_reserve.
+                                                     initial_amount),
+                           "Activate reserve failed\n");
+    for (unsigned int i = 0; i < 5; ++i)
+    {
+      if (i % 2 == 0)
+      {
+        TEST_RET_ON_FAIL (test_authorize_tip (&cls->instance,
+                                              &cls->expired_reserve,
+                                              &cls->tips[i]));
+      }
+      else
+      {
+        TEST_RET_ON_FAIL (test_authorize_tip (&cls->instance,
+                                              &cls->reserve,
+                                              &cls->tips[i]));
+      }
+    }
+    GNUNET_memcpy (&expected_tips[1],
+                   cls->tips,
+                   sizeof (struct TipData) * 5);
+    /* Test lookup tips inc */
+    TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
+                                        TALER_EXCHANGE_YNA_ALL,
+                                        6,
+                                        0,
+                                        6,
+                                        expected_tips));
+    reverse_tip_data_array (6,
+                            expected_tips);
+    /* Test lookup tips dec */
+    TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
+                                        TALER_EXCHANGE_YNA_ALL,
+                                        -6,
+                                        10,
+                                        6,
+                                        expected_tips));
+    /* Test lookup tips expired inc */
+    expected_tips[0] = cls->tips[0];
+    expected_tips[1] = cls->tips[2];
+    expected_tips[2] = cls->tips[4];
+    TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
+                                        TALER_EXCHANGE_YNA_YES,
+                                        6,
+                                        0,
+                                        3,
+                                        expected_tips));
+    /* Test lookup tips expired dec */
+    reverse_tip_data_array (3,
+                            expected_tips);
+    TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
+                                        TALER_EXCHANGE_YNA_YES,
+                                        -6,
+                                        10,
+                                        3,
+                                        expected_tips));
+    /* Test lookup tips unexpired inc */
+    expected_tips[0] = cls->tip;
+    expected_tips[1] = cls->tips[1];
+    expected_tips[2] = cls->tips[3];
+    TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
+                                        TALER_EXCHANGE_YNA_NO,
+                                        6,
+                                        0,
+                                        3,
+                                        expected_tips));
+    /* Test lookup tips unexpired dec */
+    reverse_tip_data_array (3,
+                            expected_tips);
+    TEST_RET_ON_FAIL (test_lookup_tips (&cls->instance,
+                                        TALER_EXCHANGE_YNA_NO,
+                                        -6,
+                                        10,
+                                        3,
+                                        expected_tips));
   }
+  /* Test delete reserve private key */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->delete_reserve (plugin->cls,
+                                                 cls->instance.instance.id,
+                                                 &cls->reserve.reserve_pub),
+                         "Delete reserve private key failed\n");
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+                         plugin->delete_reserve (plugin->cls,
+                                                 cls->instance.instance.id,
+                                                 &cls->reserve.reserve_pub),
+                         "Delete reserve private key failed\n");
+  /* Test purging a reserve */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->purge_reserve (plugin->cls,
+                                                cls->instance.instance.id,
+                                                &cls->reserve.reserve_pub),
+                         "Purge reserve failed\n");
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+                         plugin->purge_reserve (plugin->cls,
+                                                cls->instance.instance.id,
+                                                &cls->reserve.reserve_pub),
+                         "Purge reserve failed\n");
 
-  /* 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_TR (plugin->cls,
-                             &inc,
-                             &tip_id,
-                             &pickup_id,
-                             &pres))
+  return 0;
+}
+
+
+static int
+test_tips (void *cls)
+{
+  struct TestTips_Closure test_cls;
+  pre_test_tips (&test_cls);
+  int test_result = run_test_tips (&test_cls);
+  post_test_tips (&test_cls);
+  return test_result;
+}
+
+
+struct TestLookupRefunds_Closure
+{
+  unsigned int refunds_to_cmp_length;
+
+  const struct TALER_CoinSpendPublicKeyP *coin_pub_to_cmp;
+
+  const struct TALER_Amount *refund_amount_to_cmp;
+
+  unsigned int *results_matching;
+
+  unsigned int results_length;
+};
+
+
+static void
+lookup_refunds_cb (void *cls,
+                   const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                   const struct TALER_Amount *refund_amount)
+{
+  struct TestLookupRefunds_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    if ((0 == GNUNET_memcmp (&cmp->coin_pub_to_cmp[i],
+                             coin_pub)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (&cmp->refund_amount_to_cmp[i],
+                                                 refund_amount)) &&
+        (0 == TALER_amount_cmp (&cmp->refund_amount_to_cmp[i],
+                                refund_amount)))
+    {
+      cmp->results_matching[i] += 1;
+    }
   }
-  if (0 != GNUNET_memcmp (&pres,
-                          &tip_reserve_priv))
+}
+
+
+static int
+test_lookup_refunds (const char *instance_id,
+                     const struct GNUNET_HashCode *h_contract_terms,
+                     unsigned int refunds_length,
+                     const struct TALER_CoinSpendPublicKeyP *coin_pubs,
+                     const struct TALER_Amount *refund_amounts)
+{
+  unsigned int results_matching[refunds_length];
+  struct TestLookupRefunds_Closure cmp = {
+    .refunds_to_cmp_length = refunds_length,
+    .coin_pub_to_cmp = coin_pubs,
+    .refund_amount_to_cmp = refund_amounts,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
+  if (1 != plugin->lookup_refunds (plugin->cls,
+                                   instance_id,
+                                   h_contract_terms,
+                                   &lookup_refunds_cb,
+                                   &cmp))
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup refunds failed\n");
+    return 1;
   }
-  RND_BLK (&pickup_id);
-  if (TALER_EC_NONE !=
-      plugin->pickup_tip_TR (plugin->cls,
-                             &inc,
-                             &tip_id,
-                             &pickup_id,
-                             &pres))
+  if (refunds_length != cmp.results_length)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup refunds failed: incorrect number of results 
returned\n");
+    return 1;
   }
-  if (0 != GNUNET_memcmp (&pres,
-                          &tip_reserve_priv))
+  for (unsigned int i = 0; refunds_length > i; ++i)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup refunds failed: mismatched data\n");
+      return 1;
+    }
   }
+  return 0;
+}
+
+
+struct RefundData
+{
+  struct GNUNET_TIME_Absolute timestamp;
+  const char *reason;
+  struct TALER_Amount refund_amount;
+  const struct TALER_CoinSpendPublicKeyP *coin_pub;
+  const char *exchange_url;
+};
+
+
+static void
+make_refund (const struct DepositData *deposit,
+             struct RefundData *refund)
+{
+  refund->timestamp = GNUNET_TIME_absolute_get ();
+  refund->reason = "some reason";
+  refund->refund_amount = deposit->amount_with_fee;
+  refund->coin_pub = &deposit->coin_pub;
+  refund->exchange_url = deposit->exchange_url;
+}
+
+
+struct RefundProofData
+{
+  struct TALER_Amount refund_fee;
+  struct TALER_ExchangeSignatureP exchange_sig;
+};
 
-  /* 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_TR (plugin->cls,
-                             &inc,
-                             &tip_id,
-                             &pickup_id,
-                             &pres))
+
+struct TestLookupRefundsDetailed_Closure
+{
+  unsigned int refunds_to_cmp_length;
+
+  const struct RefundData *refunds_to_cmp;
+
+  unsigned int *results_matching;
+
+  unsigned int results_length;
+};
+
+
+static void
+lookup_refunds_detailed_cb (void *cls,
+                            uint64_t refund_serial,
+                            struct GNUNET_TIME_Absolute timestamp,
+                            const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                            const char *exchange_url,
+                            uint64_t rtransaction_id,
+                            const char *reason,
+                            const struct TALER_Amount *refund_amount)
+{
+  struct TestLookupRefundsDetailed_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  cmp->results_length += 1;
+  for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    if ((cmp->refunds_to_cmp[i].timestamp.abs_value_us ==
+         timestamp.abs_value_us) &&
+        (0 == GNUNET_memcmp (cmp->refunds_to_cmp[i].coin_pub,
+                             coin_pub)) &&
+        (0 == strcmp (cmp->refunds_to_cmp[i].exchange_url,
+                      exchange_url)) &&
+        (0 == strcmp (cmp->refunds_to_cmp[i].reason,
+                      reason)) &&
+        (GNUNET_OK == TALER_amount_cmp_currency (
+           &cmp->refunds_to_cmp[i].refund_amount,
+           refund_amount)) &&
+        (0 == TALER_amount_cmp (&cmp->refunds_to_cmp[i].refund_amount,
+                                refund_amount)))
+    {
+      cmp->results_matching[i] += 1;
+    }
   }
+}
 
-  /* 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_TR (plugin->cls,
-                                "testing tips insufficient funds",
-                                json_object (),
-                                &amount,
-                                &tip_reserve_priv,
-                                "http://localhost:8081/";,
-                                &tip_expiration,
-                                &tip_id))
+
+static int
+test_lookup_refunds_detailed (const char *instance_id,
+                              const struct GNUNET_HashCode *h_contract_terms,
+                              unsigned int refunds_length,
+                              const struct RefundData *refunds)
+{
+  unsigned int results_matching[refunds_length];
+  struct TestLookupRefundsDetailed_Closure cmp = {
+    .refunds_to_cmp_length = refunds_length,
+    .refunds_to_cmp = refunds,
+    .results_matching = results_matching,
+    .results_length = 0
+  };
+  memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
+  if (1 != plugin->lookup_refunds_detailed (plugin->cls,
+                                            instance_id,
+                                            h_contract_terms,
+                                            &lookup_refunds_detailed_cb,
+                                            &cmp))
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup refunds detailed failed\n");
+    return 1;
+  }
+  if (refunds_length != cmp.results_length)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup refunds detailed failed: incorrect number of 
results\n");
+    return 1;
   }
+  for (unsigned int i = 0; refunds_length > i; ++i)
+  {
+    if (1 != cmp.results_matching[i])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Lookup refunds detailed failed: mismatched data\n");
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+struct LookupRefundSerial_Closure
+{
+  const struct RefundData *refund;
+
+  uint64_t serial;
+};
+
+
+static void
+get_refund_serial_cb (void *cls,
+                      uint64_t refund_serial,
+                      struct GNUNET_TIME_Absolute timestamp,
+                      const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                      const char *exchange_url,
+                      uint64_t rtransaction_id,
+                      const char *reason,
+                      const struct TALER_Amount *refund_amount)
+{
+  struct LookupRefundSerial_Closure *lookup_cls = cls;
+  if (NULL == lookup_cls)
+    return;
+  if ((lookup_cls->refund->timestamp.abs_value_us ==
+       timestamp.abs_value_us) &&
+      (0 == GNUNET_memcmp (lookup_cls->refund->coin_pub,
+                           coin_pub)) &&
+      (0 == strcmp (lookup_cls->refund->exchange_url,
+                    exchange_url)) &&
+      (0 == strcmp (lookup_cls->refund->reason,
+                    reason)) &&
+      (GNUNET_OK == TALER_amount_cmp_currency (
+         &lookup_cls->refund->refund_amount,
+         refund_amount)) &&
+      (0 == TALER_amount_cmp (&lookup_cls->refund->refund_amount,
+                              refund_amount)))
+    lookup_cls->serial = refund_serial;
+}
+
+
+static uint64_t
+get_refund_serial (const struct InstanceData *instance,
+                   const struct GNUNET_HashCode *h_contract_terms,
+                   const struct RefundData *refund)
+{
+  struct LookupRefundSerial_Closure lookup_cls = {
+    .refund = refund,
+    .serial = 0
+  };
+
+  GNUNET_assert (0 < plugin->lookup_refunds_detailed (plugin->cls,
+                                                      instance->instance.id,
+                                                      h_contract_terms,
+                                                      &get_refund_serial_cb,
+                                                      &lookup_cls));
+  GNUNET_assert (0 != lookup_cls.serial);
+
+  return lookup_cls.serial;
+}
+
 
-  /* 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_TR (plugin->cls,
-                             &inc,
-                             &tip_id,
-                             &pickup_id,
-                             &pres))
+static int
+test_lookup_refund_proof (uint64_t refund_serial,
+                          const struct
+                          TALER_ExchangeSignatureP *expected_exchange_sig,
+                          const struct
+                          TALER_ExchangePublicKeyP *expected_exchange_pub)
+{
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  if (1 != plugin->lookup_refund_proof (plugin->cls,
+                                        refund_serial,
+                                        &exchange_sig,
+                                        &exchange_pub))
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup refund proof failed\n");
+    return 1;
+  }
+  if ((0 != GNUNET_memcmp (expected_exchange_sig,
+                           &exchange_sig)) ||
+      (0 != GNUNET_memcmp (expected_exchange_pub,
+                           &exchange_pub)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Lookup refund proof failed: mismatched data\n");
+    return 1;
+  }
+  return 0;
+}
+
+
+struct TestRefunds_Closure
+{
+  struct InstanceData instance;
+
+  /**
+   * The merchant account
+   */
+  struct TALER_MERCHANTDB_AccountDetails account;
+
+  /**
+   * The exchange signing key
+   */
+  struct ExchangeSignkeyData signkey;
+
+  /**
+   * The order data
+   */
+  struct OrderData order;
+
+  /**
+   * The deposit data
+   */
+  struct DepositData deposits[2];
+
+  /**
+   * The refund data
+   */
+  struct RefundData refund;
+
+  /**
+   * The refund proof data
+   */
+  struct RefundProofData refund_proof;
+};
+
+
+static void
+pre_test_refunds (struct TestRefunds_Closure *cls)
+{
+  struct TALER_RefundRequestPS refund_sign = {
+    .purpose = {
+      .size = htonl (sizeof (struct TALER_RefundRequestPS)),
+      .purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND)
+    }
+
+
+  };
+
+  /* Instance */
+  make_instance ("test_inst_refunds",
+                 &cls->instance);
+
+  /* Account */
+  make_account (&cls->account);
+
+  /* Signing key */
+  make_exchange_signkey (&cls->signkey);
+
+  /* Order */
+  make_order ("test_refunds_od_0",
+              &cls->order);
+
+  /* Deposit */
+  make_deposit (&cls->instance,
+                &cls->account,
+                &cls->order,
+                &cls->signkey,
+                &cls->deposits[0]);
+  make_deposit (&cls->instance,
+                &cls->account,
+                &cls->order,
+                &cls->signkey,
+                &cls->deposits[1]);
+
+  /* Refund */
+  make_refund (&cls->deposits[0],
+               &cls->refund);
+
+  /* Refund proof */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.02",
+                                         &cls->refund_proof.refund_fee));
+  GNUNET_CRYPTO_eddsa_sign (&cls->signkey.exchange_priv.eddsa_priv,
+                            &refund_sign,
+                            &cls->refund_proof.exchange_sig.eddsa_signature);
+}
+
+
+static void
+post_test_refunds (struct TestRefunds_Closure *cls)
+{
+  free_instance_data (&cls->instance);
+  free_order_data (&cls->order);
+}
+
+
+static int
+run_test_refunds (struct TestRefunds_Closure *cls)
+{
+  struct TALER_Amount inc;
+  uint64_t refund_serial;
+
+  /* Insert an instance */
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert an account */
+  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
+                                         &cls->account,
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert an order */
+  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                       &cls->order,
+                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert contract terms */
+  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
+                                                &cls->order,
+                                                
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert a signing key */
+  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
+                                                  
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Insert a deposit */
+  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                         &cls->signkey,
+                                         &cls->deposits[0],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                         &cls->signkey,
+                                         &cls->deposits[1],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Mark as paid */
+  TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
+                                             &cls->order,
+                                             
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  /* Test refund coin */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->refund_coin (plugin->cls,
+                                              cls->instance.instance.id,
+                                              
&cls->deposits[0].h_contract_terms,
+                                              cls->refund.timestamp,
+                                              cls->refund.coin_pub,
+                                              cls->refund.reason),
+                         "Refund coin failed\n");
+  refund_serial = get_refund_serial (&cls->instance,
+                                     &cls->deposits[0].h_contract_terms,
+                                     &cls->refund);
+  /* Test double refund fails */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+                         plugin->refund_coin (plugin->cls,
+                                              cls->instance.instance.id,
+                                              
&cls->deposits[0].h_contract_terms,
+                                              cls->refund.timestamp,
+                                              cls->refund.coin_pub,
+                                              cls->refund.reason),
+                         "Refund coin failed\n");
+  /* Test lookup refunds */
+  TEST_RET_ON_FAIL (test_lookup_refunds (cls->instance.instance.id,
+                                         &cls->deposits[0].h_contract_terms,
+                                         1,
+                                         cls->refund.coin_pub,
+                                         &cls->refund.refund_amount));
+  /* Test lookup refunds detailed */
+  TEST_RET_ON_FAIL (test_lookup_refunds_detailed (cls->instance.instance.id,
+                                                  &cls->deposits[0].
+                                                  h_contract_terms,
+                                                  1,
+                                                  &cls->refund));
+  /* Test insert refund proof */
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                         plugin->insert_refund_proof (plugin->cls,
+                                                      refund_serial,
+                                                      &cls->refund_proof.
+                                                      refund_fee,
+                                                      &cls->refund_proof.
+                                                      exchange_sig,
+                                                      
&cls->signkey.exchange_pub),
+                         "Insert refund proof failed\n");
+  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
+                         plugin->insert_refund_proof (plugin->cls,
+                                                      refund_serial,
+                                                      &cls->refund_proof.
+                                                      refund_fee,
+                                                      &cls->refund_proof.
+                                                      exchange_sig,
+                                                      
&cls->signkey.exchange_pub),
+                         "Insert refund proof failed\n");
+  /* Test that we can't give too much in refunds */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1000.00",
+                                         &inc));
+  TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_TOO_HIGH ==
+                         plugin->increase_refund (plugin->cls,
+                                                  cls->instance.instance.id,
+                                                  cls->order.id,
+                                                  &inc,
+                                                  "more"),
+                         "Increase refund failed\n");
+  /* Test increase refund */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1.00",
+                                         &inc));
+  TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
+                         plugin->increase_refund (plugin->cls,
+                                                  cls->instance.instance.id,
+                                                  cls->order.id,
+                                                  &inc,
+                                                  "more"),
+                         "Increase refund failed\n");
+  /* Test lookup refund proof */
+  TEST_RET_ON_FAIL (test_lookup_refund_proof (1,
+                                              &cls->refund_proof.exchange_sig,
+                                              &cls->signkey.exchange_pub));
+
+  return 0;
+}
+
+
+static int
+test_refunds (void *cls)
+{
+  struct TestRefunds_Closure test_cls;
+  pre_test_refunds (&test_cls);
+  int test_result = run_test_refunds (&test_cls);
+  post_test_refunds (&test_cls);
+  return test_result;
+}
+
+
+/**
+ * Convenience function for testing lookup orders with filters
+ */
+static void
+reverse_order_data_array (unsigned int orders_length,
+                          struct OrderData *orders)
+{
+  struct OrderData tmp[orders_length];
+  for (unsigned int i = 0; i < orders_length; ++i)
+    tmp[i] = orders[orders_length - 1 - i];
+  for (unsigned int i = 0; i < orders_length; ++i)
+    orders[i] = tmp[i];
+}
+
+
+struct TestLookupOrdersAllFilters_Closure
+{
+  struct InstanceData instance;
+  struct TALER_MERCHANTDB_AccountDetails account;
+  struct ExchangeSignkeyData signkey;
+
+  char *order_ids[64];
+  struct OrderData orders[64];
+  struct DepositData deposits[64];
+  struct RefundData refunds[64];
+};
+
+
+static void
+pre_test_lookup_orders_all_filters (struct
+                                    TestLookupOrdersAllFilters_Closure *cls)
+{
+  make_instance ("test_inst_lookup_orders_all_filters",
+                 &cls->instance);
+  make_account (&cls->account);
+  make_exchange_signkey (&cls->signkey);
+  for (unsigned int i = 0; i < 64; ++i)
+  {
+    (void) GNUNET_asprintf (&cls->order_ids[i],
+                            "test_orders_filter_od_%u",
+                            i);
+    make_order (cls->order_ids[i],
+                &cls->orders[i]);
+    GNUNET_assert (0 == json_object_set (cls->orders[i].contract,
+                                         "order_id",
+                                         json_string (cls->order_ids[i])));
+    make_deposit (&cls->instance,
+                  &cls->account,
+                  &cls->orders[i],
+                  &cls->signkey,
+                  &cls->deposits[i]);
+    make_refund (&cls->deposits[i],
+                 &cls->refunds[i]);
+  }
+}
+
+
+static void
+post_test_lookup_orders_all_filters (struct
+                                     TestLookupOrdersAllFilters_Closure *cls)
+{
+  free_instance_data (&cls->instance);
+  for (unsigned int i = 0; i < 64; ++i)
+  {
+    free_order_data (&cls->orders[i]);
+    GNUNET_free (cls->order_ids[i]);
+  }
+}
+
+
+static int
+run_test_lookup_orders_all_filters (struct
+                                    TestLookupOrdersAllFilters_Closure *cls)
+{
+  /* Order filter extravaganza */
+  struct
+  {
+    bool claimed;
+    bool paid;
+    bool refunded;
+    bool wired;
+  } order_status[64];
+  unsigned int *permutation;
+
+  /* Pseudorandomly generate variations for the filter to differentiate */
+  GNUNET_CRYPTO_seed_weak_random (1);
+  permutation = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK,
+                                              64);
+  for (unsigned int i = 0; i < 64; ++i)
+  {
+    unsigned int dest = permutation[i];
+    order_status[dest].claimed = (i & 1) ? true : false;
+    order_status[dest].paid = (3 == (i & 3)) ? true : false;
+    order_status[dest].refunded = (5 == (i & 5)) ? true : false;
+    order_status[dest].wired = (9 == (i & 9)) ? true : false;
+  }
+  GNUNET_free (permutation);
+
+
+  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
+                                          
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
+                                         &cls->account,
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
+                                                  
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  for (unsigned int i = 0; i < 64; ++i)
+  {
+    uint64_t order_serial;
+
+    TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
+                                         &cls->orders[i],
+                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+    order_serial = get_order_serial (&cls->instance,
+                                     &cls->orders[i]);
+
+    if (order_status[i].claimed)
+    {
+      TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
+                                                    &cls->orders[i],
+                                                    
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+    }
+    else
+    {
+      continue;
+    }
+
+    if (order_status[i].paid)
+      TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
+                                                 &cls->orders[i],
+                                                 
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+    if (order_status[i].refunded)
+    {
+      TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
+                                             &cls->signkey,
+                                             &cls->deposits[i],
+                                             
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+      TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                             plugin->refund_coin (plugin->cls,
+                                                  cls->instance.instance.id,
+                                                  &cls->deposits[i].
+                                                  h_contract_terms,
+                                                  cls->refunds[i].timestamp,
+                                                  cls->refunds[i].coin_pub,
+                                                  cls->refunds[i].reason),
+                             "Refund coin failed\n");
+    }
+
+    if (order_status[i].wired)
+      TEST_RET_ON_FAIL (test_mark_order_wired (order_serial,
+                                               
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+  }
+
+  /* There are 3^3 = 27 possibilities here, not counting inc/dec and start row 
*/
+  for (unsigned int i = 0; i < 27; ++i)
+  {
+    struct TALER_MERCHANTDB_OrderFilter filter = {
+      .paid = (i % 3) + 1,
+      .refunded = ((i / 3) % 3) + 1,
+      .wired = ((i / 9) % 3) + 1,
+      .date = GNUNET_TIME_absolute_get_zero_ (),
+      .start_row = 0,
+      .delta = 64
+    };
+    unsigned int orders_length = 0;
+    struct OrderData orders[64];
+
+    /* Now figure out which orders should make it through the filter */
+    for (unsigned int j = 0; j < 64; ++j)
+    {
+      if (((TALER_EXCHANGE_YNA_YES == filter.paid) &&
+           (true != order_status[j].paid)) ||
+          ((TALER_EXCHANGE_YNA_NO == filter.paid) &&
+           (false != order_status[j].paid)) ||
+          ((TALER_EXCHANGE_YNA_YES == filter.refunded) &&
+           (true != order_status[j].refunded)) ||
+          ((TALER_EXCHANGE_YNA_NO == filter.refunded) &&
+           (false != order_status[j].refunded)) ||
+          ((TALER_EXCHANGE_YNA_YES == filter.wired) &&
+           (true != order_status[j].wired)) ||
+          ((TALER_EXCHANGE_YNA_NO == filter.wired) &&
+           (false != order_status[j].wired)))
+        continue;
+      orders[orders_length] = cls->orders[j];
+      orders_length += 1;
+    }
+
+    /* Test the lookup */
+    TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
+                                          &filter,
+                                          orders_length,
+                                          orders));
+
+    /* Now test decreasing */
+    filter.start_row = 256;
+    filter.date = GNUNET_TIME_UNIT_FOREVER_ABS;
+    filter.delta = -64;
+
+    reverse_order_data_array (orders_length,
+                              orders);
+
+    TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
+                                          &filter,
+                                          orders_length,
+                                          orders));
   }
 
-  return GNUNET_OK;
+  return 0;
+}
+
+
+/* This has to happen after everything else because it
+   depends on deposits, etc. */
+static int
+test_lookup_orders_all_filters (void *cls)
+{
+  struct TestLookupOrdersAllFilters_Closure test_cls;
+  pre_test_lookup_orders_all_filters (&test_cls);
+  int test_result = run_test_lookup_orders_all_filters (&test_cls);
+  post_test_lookup_orders_all_filters (&test_cls);
+  return test_result;
+}
+
+
+/**
+ * Function that runs all tests and returns 1 upon error, 0 on success.
+ */
+static int
+run_tests (void *cls)
+{
+  TEST_RET_ON_FAIL (test_instances ());
+  TEST_RET_ON_FAIL (test_products ());
+  TEST_RET_ON_FAIL (test_orders ());
+  TEST_RET_ON_FAIL (test_deposits ());
+  TEST_RET_ON_FAIL (test_transfers ());
+  TEST_RET_ON_FAIL (test_tips (cls));
+  TEST_RET_ON_FAIL (test_refunds (cls));
+  TEST_RET_ON_FAIL (test_lookup_orders_all_filters (cls));
+
+  return 0;
 }
 
 
@@ -744,15 +5998,32 @@ static void
 run (void *cls)
 {
   struct GNUNET_CONFIGURATION_Handle *cfg = cls;
-  struct GNUNET_TIME_Absolute fake_now;
-  json_t *out;
   /* Data for 'store_payment()' */
 
+  /* Drop the tables to cleanup anything that might cause issues */
+  if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
+  {
+    result = 77;
+    return;
+  }
+
+  GNUNET_break (GNUNET_OK ==
+                plugin->drop_tables (plugin->cls));
+  TALER_MERCHANTDB_plugin_unload (plugin);
+
+  /* Load the plugin */
   if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
   {
     result = 77;
     return;
   }
+
+  /* Run the preflight */
+  plugin->preflight (plugin->cls);
+
+  result = run_tests (cls);
+
+  /* Test dropping tables */
   if (GNUNET_OK != plugin->drop_tables (plugin->cls))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -760,274 +6031,21 @@ run (void *cls)
     result = 77;
     return;
   }
+
+  /* Unload the plugin */
   TALER_MERCHANTDB_plugin_unload (plugin);
   if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
   {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Plugin unload failed\n");
     result = 77;
     return;
   }
 
-  /* Prepare data for 'store_payment()' */
-  RND_BLK (&h_wire);
-  RND_BLK (&h_contract_terms);
-  order_id = "test_ID";
-  order_id_future = "test_ID_future";
-  RND_BLK (&signkey_pub);
-  RND_BLK (&merchant_pub);
-  RND_BLK (&wtid);
-  timestamp = GNUNET_TIME_absolute_get ();
-  (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 ();
-  (void) GNUNET_TIME_round_abs (&refund_deadline);
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":5",
-                                         &amount_with_fee));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":0.000010",
-                                         &deposit_fee));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":0.000001",
-                                         &wire_fee));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":0.000010",
-                                         &refund_fee));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":2",
-                                         &refund_amount));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":1",
-                                         &little_refund_amount));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":3",
-                                         &right_second_refund_amount));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (CURRENCY ":30",
-                                         &too_big_refund_amount));
-  RND_BLK (&coin_pub);
-  deposit_proof = json_object ();
-  GNUNET_assert (0 ==
-                 json_object_set_new (deposit_proof,
-                                      "x-taler-bank",
-                                      json_string ("backenddb test A")));
-  transfer_proof = json_object ();
-  GNUNET_assert (0 ==
-                 json_object_set_new (transfer_proof,
-                                      "x-taler-bank",
-                                      json_string ("backenddb test B")));
-  contract = json_object ();
-  contract_terms = json_object ();
-  GNUNET_assert (0 ==
-                 json_object_set_new (contract_terms,
-                                      "order",
-                                      json_string ("1")));
-
-  contract_terms_future = json_object ();
-  GNUNET_assert (0 ==
-                 json_object_set_new (contract_terms_future,
-                                      "order",
-                                      json_string ("2")));
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_JSON_hash (contract_terms,
-                                  &h_contract_terms));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->insert_contract_terms (plugin->cls,
-                                         order_id,
-                                         &merchant_pub,
-                                         timestamp,
-                                         contract_terms));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
-          plugin->find_paid_contract_terms_from_hash (plugin->cls,
-                                                      &out,
-                                                      &h_contract_terms,
-                                                      &merchant_pub));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->mark_proposal_paid (plugin->cls,
-                                      &h_contract_terms,
-                                      &merchant_pub));
-
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->mark_proposal_paid (plugin->cls,
-                                      &h_contract_terms,
-                                      &merchant_pub));
-
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->find_contract_terms_history (plugin->cls,
-                                               order_id,
-                                               &merchant_pub,
-                                               &pd_cb,
-                                               NULL));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->find_paid_contract_terms_from_hash (plugin->cls,
-                                                      &out,
-                                                      &h_contract_terms,
-                                                      &merchant_pub));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->find_contract_terms_from_hash (plugin->cls,
-                                                 &out,
-                                                 &h_contract_terms,
-                                                 &merchant_pub));
-  FAILIF (1 !=
-          plugin->find_contract_terms_by_date_and_range (plugin->cls,
-                                                         fake_now,
-                                                         &merchant_pub,
-                                                         2,
-                                                         1,
-                                                         GNUNET_YES,
-                                                         GNUNET_NO,
-                                                         &pd_cb,
-                                                         NULL));
-  timestamp = GNUNET_TIME_absolute_get ();
-  (void) GNUNET_TIME_round_abs (&timestamp);
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->insert_contract_terms (plugin->cls,
-                                         order_id_future,
-                                         &merchant_pub,
-                                         timestamp,
-                                         contract_terms_future));
-
-  fake_now = GNUNET_TIME_absolute_subtract (timestamp, delta);
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_JSON_hash (contract_terms_future,
-                                  &h_contract_terms_future));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->mark_proposal_paid (plugin->cls,
-                                      &h_contract_terms_future,
-                                      &merchant_pub));
-  FAILIF (2 !=
-          plugin->find_contract_terms_by_date_and_range (plugin->cls,
-                                                         fake_now,
-                                                         &merchant_pub,
-                                                         0,
-                                                         5,
-                                                         GNUNET_NO,
-                                                         GNUNET_NO,
-                                                         &pd_cb,
-                                                         NULL));
-
-  FAILIF (0 !=
-          plugin->find_contract_terms_by_date (plugin->cls,
-                                               fake_now,
-                                               &merchant_pub,
-                                               1,
-                                               &pd_cb,
-                                               NULL));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->store_deposit (plugin->cls,
-                                 &h_contract_terms,
-                                 &merchant_pub,
-                                 &coin_pub,
-                                 EXCHANGE_URL,
-                                 &amount_with_fee,
-                                 &deposit_fee,
-                                 &refund_fee,
-                                 &wire_fee,
-                                 &signkey_pub,
-                                 deposit_proof));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->store_coin_to_transfer (plugin->cls,
-                                          &h_contract_terms,
-                                          &coin_pub,
-                                          &wtid));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->store_transfer_to_proof (plugin->cls,
-                                           EXCHANGE_URL,
-                                           &wtid,
-                                           GNUNET_TIME_UNIT_ZERO_ABS,
-                                           &signkey_pub,
-                                           transfer_proof));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->find_payments (plugin->cls,
-                                 &h_contract_terms,
-                                 &merchant_pub,
-                                 &deposit_cb,
-                                 NULL));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->find_transfers_by_hash (plugin->cls,
-                                          &h_contract_terms,
-                                          &transfer_cb,
-                                          NULL));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->find_deposits_by_wtid (plugin->cls,
-                                         &wtid,
-                                         &deposit_cb,
-                                         NULL));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->find_proof_by_wtid (plugin->cls,
-                                      EXCHANGE_URL,
-                                      &wtid,
-                                      &proof_cb,
-                                      NULL));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
-          plugin->get_refunds_from_contract_terms_hash (plugin->cls,
-                                                        &merchant_pub,
-                                                        &h_contract_terms,
-                                                        &refund_cb,
-                                                        NULL));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->increase_refund_for_contract_NT (plugin->cls,
-                                                   &h_contract_terms,
-                                                   &merchant_pub,
-                                                   &refund_amount,
-                                                   "refund testing"));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->increase_refund_for_contract_NT (plugin->cls,
-                                                   &h_contract_terms,
-                                                   &merchant_pub,
-                                                   &refund_amount,
-                                                   "same refund amount as "
-                                                   "the previous one, should 
succeed without changes (1)"));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->increase_refund_for_contract_NT (plugin->cls,
-                                                   &h_contract_terms,
-                                                   &merchant_pub,
-                                                   &little_refund_amount,
-                                                   "lower refund amount as the 
previous one, should succeed without changes (1)"));
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->increase_refund_for_contract_NT (plugin->cls,
-                                                   &h_contract_terms,
-                                                   &merchant_pub,
-                                                   &right_second_refund_amount,
-                                                   "right refund increase"));
-
-  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
-          plugin->increase_refund_for_contract_NT (plugin->cls,
-                                                   &h_contract_terms,
-                                                   &merchant_pub,
-                                                   &too_big_refund_amount,
-                                                   "make refund testing fail 
due to too big refund amount"));
-
-  FAILIF (GNUNET_OK !=
-          test_wire_fee ());
-  FAILIF (GNUNET_OK !=
-          test_tipping ());
-
-
-  if (-1 == result)
-    result = 0;
-
-drop:
   GNUNET_break (GNUNET_OK ==
                 plugin->drop_tables (plugin->cls));
   TALER_MERCHANTDB_plugin_unload (plugin);
   plugin = NULL;
-  if (NULL != deposit_proof)
-    json_decref (deposit_proof);
-  if (NULL != transfer_proof)
-    json_decref (transfer_proof);
 }
 
 
@@ -1041,7 +6059,8 @@ main (int argc,
   struct GNUNET_CONFIGURATION_Handle *cfg;
 
   result = -1;
-  if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+  if (NULL == (plugin_name = strrchr (argv[0],
+                                      (int) '-')))
   {
     GNUNET_break (0);
     return -1;
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index 71b69f5..42cecb9 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -121,6 +121,21 @@ TALER_MERCHANT_parse_error_details_ (const json_t 
*response,
                                      struct TALER_MERCHANT_HttpResponse *hr);
 
 
+/**
+ * Construct a new base URL using the existing @a base_url
+ * and the given @a instance_id.  The result WILL end with
+ * '/'.
+ *
+ * @param base_url a merchant base URL without "/instances/" in it,
+ *         must not be the empty string; MAY end with '/'.
+ * @param instance_id ID of an instance
+ * @return "${base_url}/instances/${instance_id}/"
+ */
+char *
+TALER_MERCHANT_baseurl_add_instance (const char *base_url,
+                                     const char *instance_id);
+
+
 /* ********************* /public/config ****************** */
 
 
@@ -176,38 +191,6 @@ enum TALER_MERCHANT_VersionCompatibility
 };
 
 
-/**
- * @brief Information about a merchant instance.
- */
-struct TALER_MERCHANT_InstanceInformation
-{
-  /**
-   * URL of this instance.  The URL can be relative to the current domain
-   * (i.e. "/PizzaShop/") or include a schema and fully qualified domain name
-   * (i.e. "https://backend.example.com/PS/";). The latter can be used to 
redirect
-   * clients to a different server in case the deployment location changes.
-   */
-  const char *instance_baseurl;
-
-  /**
-   * Legal name of the merchant/instance.
-   */
-  const char *name;
-
-  /**
-   * Base URL of the exchange this instance uses for tipping, or NULL if this
-   * instance does not support tipping.
-   */
-  const char *tipping_exchange_baseurl;
-
-  /**
-   * Public key of the instance.
-   */
-  struct TALER_MerchantPublicKeyP merchant_pub;
-
-};
-
-
 /**
  * @brief Config information we get from the backend.
  */
@@ -226,16 +209,6 @@ struct TALER_MERCHANT_ConfigInformation
    */
   const char *version;
 
-  /**
-   * Array with information about the merchant's instances.
-   */
-  struct TALER_MERCHANT_InstanceInformation *iis;
-
-  /**
-   * Length of the @e iis array.
-   */
-  unsigned int iis_len;
-
 };
 
 
@@ -290,452 +263,1735 @@ void
 TALER_MERCHANT_config_get_cancel (struct TALER_MERCHANT_ConfigGetHandle *vgh);
 
 
-/* ********************* /refund ************************** */
-
-/**
- * Handle for a GET /refund operation.
- */
-struct TALER_MERCHANT_RefundLookupOperation;
+/* ********************* /instances *********************** */
 
 
 /**
- * Detail about a refund lookup result.
+ * @brief Information about a merchant instance.
  */
-struct TALER_MERCHANT_RefundDetail
+struct TALER_MERCHANT_InstanceInformation
 {
-
-  /**
-   * Exchange response details.  Full details are only included
-   * upon failure (HTTP status is not #MHD_HTTP_OK).
-   */
-  struct TALER_EXCHANGE_HttpResponse hr;
-
   /**
-   * Coin this detail is about.
+   * Id of this instance.  This $ID can be used to construct the URL of the
+   * instance, by combining it using "$MERCHANT_BASEURL/instances/$ID/".
    */
-  struct TALER_CoinSpendPublicKeyP coin_pub;
+  const char *id;
 
   /**
-   * Refund transaction ID used.
+   * Legal name of the merchant/instance.
    */
-  uint64_t rtransaction_id;
+  const char *name;
 
   /**
-   * Amount to be refunded for this coin.
+   * Public key of the instance.
    */
-  struct TALER_Amount refund_amount;
+  struct TALER_MerchantPublicKeyP merchant_pub;
 
   /**
-   * Applicable refund transaction fee.
+   * JSON array of payment targets (strings) supported by this backend
+   * instance.
    */
-  struct TALER_Amount refund_fee;
+  json_t *payment_targets;
 
-  /**
-   * Public key of the exchange affirming the refund,
-   * only valid if the @e hr http_status is #MHD_HTTP_OK.
-   */
-  struct TALER_ExchangePublicKeyP exchange_pub;
+};
 
-  /**
-   * Signature of the exchange affirming the refund,
-   * only valid if the @e hr http_status is #MHD_HTTP_OK.
-   */
-  struct TALER_ExchangeSignatureP exchange_sig;
 
-};
+/**
+ * Handle for a GET /instances operation.
+ */
+struct TALER_MERCHANT_InstancesGetHandle;
 
 
 /**
- * Callback to process a GET /refund request
+ * Function called with the result of the GET /instances operation.
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param h_contract_terms hash of the contract terms to which the refund is 
applied
- * @param merchant_pub public key of the merchant
- * @param num_details length of the @a details array
- * @param details details about the refund processing
+ * @param hr HTTP response data
+ * @param iis_length length of the @a iis array
+ * @param iis array with instance information of length @a iis_length
  */
 typedef void
-(*TALER_MERCHANT_RefundLookupCallback) (
+(*TALER_MERCHANT_InstancesGetCallback)(
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
-  const struct GNUNET_HashCode *h_contract_terms,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  unsigned int num_details,
-  const struct TALER_MERCHANT_RefundDetail *details);
+  unsigned int iis_length,
+  const struct TALER_MERCHANT_InstanceInformation iis[]);
 
 
 /**
- * Does a GET /refund.
+ * Get the instance data of a backend. Will connect to the merchant backend
+ * and obtain information about the instances.  The respective information will
+ * be passed to the @a instances_cb once available.
  *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param order_id order id used to perform the lookup
- * @param cb callback which will work the response gotten from the backend
- * @param cb_cls closure to pass to the callback
- * @return handle for this operation, NULL upon errors
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instances_cb function to call with the
+ *        backend's instances information
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
  */
-struct TALER_MERCHANT_RefundLookupOperation *
-TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
+struct TALER_MERCHANT_InstancesGetHandle *
+TALER_MERCHANT_instances_get (struct GNUNET_CURL_Context *ctx,
                               const char *backend_url,
-                              const char *order_id,
-                              TALER_MERCHANT_RefundLookupCallback cb,
-                              void *cb_cls);
+                              TALER_MERCHANT_InstancesGetCallback instances_cb,
+                              void *instances_cb_cls);
+
 
 /**
- * Cancel a GET /refund request.
+ * Cancel /instances request.  Must not be called by clients after
+ * the callback was invoked.
  *
- * @param rlo the refund increasing operation to cancel
+ * @param igh request to cancel.
  */
 void
-TALER_MERCHANT_refund_lookup_cancel (
-  struct TALER_MERCHANT_RefundLookupOperation *rlo);
+TALER_MERCHANT_instances_get_cancel (
+  struct TALER_MERCHANT_InstancesGetHandle *igh);
 
 
 /**
- * Handle for a POST /refund operation.
+ * Handle for a POST /instances/$ID operation.
  */
-struct TALER_MERCHANT_RefundIncreaseOperation;
+struct TALER_MERCHANT_InstancesPostHandle;
 
 
 /**
- * Callback to process a POST /refund request
+ * Function called with the result of the GET /instances/$ID operation.
  *
  * @param cls closure
- * @param http_status HTTP status code for this request
- * @param ec taler-specific error code
+ * @param hr HTTP response data
  */
 typedef void
-(*TALER_MERCHANT_RefundIncreaseCallback) (
+(*TALER_MERCHANT_InstancesPostCallback)(
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr);
 
 
 /**
- * Increase the refund associated to a order
- *
- * @param ctx the CURL context used to connect to the backend
- * @param backend_url backend's base URL, including final "/"
- * @param order_id id of the order whose refund is to be increased
- * @param refund amount to which increase the refund
- * @param reason human-readable reason justifying the refund
- * @param cb callback processing the response from /refund
- * @param cb_cls closure for cb
- */
-struct TALER_MERCHANT_RefundIncreaseOperation *
-TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
-                                const char *backend_url,
-                                const char *order_id,
-                                const struct TALER_Amount *refund,
-                                const char *reason,
-                                TALER_MERCHANT_RefundIncreaseCallback cb,
-                                void *cb_cls);
-
-/**
- * Cancel a POST /refund request.
- *
- * @param rio the refund increasing operation to cancel
- */
-void
-TALER_MERCHANT_refund_increase_cancel (
-  struct TALER_MERCHANT_RefundIncreaseOperation *rio);
-
-
-/* *********************  /proposal *********************** */
-
-
-/**
- * Handle to a PUT /proposal operation
- */
-struct TALER_MERCHANT_ProposalOperation;
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * /contract request to a merchant.
- *
- * @param cls closure
- * @param hr HTTP response details
- * @param order_id order id of the newly created order
- */
-typedef void
-(*TALER_MERCHANT_ProposalCallback) (
-  void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr,
-  const char *order_id);
-
-
-/**
- * PUT an order to the backend and receives the related proposal.
+ * Setup an new instance in the backend.
  *
- * @param ctx execution context
- * @param backend_url URL of the backend
- * @param order basic information about this purchase, to be extended by the
- * backend
- * @param proposal_cb the callback to call when a reply for this request is 
available
- * @param proposal_cb_cls closure for @a proposal_cb
- * @return a handle for this request, NULL on error
- */
-struct TALER_MERCHANT_ProposalOperation *
-TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx,
-                          const char *backend_url,
-                          const json_t *order,
-                          TALER_MERCHANT_ProposalCallback proposal_cb,
-                          void *proposal_cb_cls);
-
-
-/**
- * Cancel a PUT /proposal request.  This function cannot be used
- * on a request handle if a response is already served for it.
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id identity of the instance to get information about
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param cb function to call with the
+ *        backend's instances information
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstancesPostHandle *
+TALER_MERCHANT_instances_post (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *instance_id,
+  unsigned int accounts_length,
+  const char *payto_uris[],
+  const char *name,
+  const json_t *address,
+  const json_t *jurisdiction,
+  const struct TALER_Amount *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const struct TALER_Amount *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  TALER_MERCHANT_InstancesPostCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel /instances request.  Must not be called by clients after
+ * the callback was invoked.
  *
- * @param po the proposal operation request handle
+ * @param igh request to cancel.
  */
 void
-TALER_MERCHANT_proposal_cancel (struct TALER_MERCHANT_ProposalOperation *po);
+TALER_MERCHANT_instances_post_cancel (
+  struct TALER_MERCHANT_InstancesPostHandle *iph);
 
 
 /**
- * Handle to a GET /proposal operation
+ * Handle for a PATCH /instances/$ID operation.
  */
-struct TALER_MERCHANT_ProposalLookupOperation;
+struct TALER_MERCHANT_InstancePatchHandle;
 
 
 /**
- * Callback called to work a GET /proposal response.
+ * Function called with the result of the GET /instances/$ID operation.
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param contract_terms the details of the contract
- * @param sig merchant's signature over @a contract_terms
- * @param contract_hash hash over @a contract_terms
+ * @param hr HTTP response data
  */
 typedef void
-(*TALER_MERCHANT_ProposalLookupOperationCallback) (
+(*TALER_MERCHANT_InstancePatchCallback)(
   void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr,
-  const json_t *contract_terms,
-  const struct TALER_MerchantSignatureP *sig,
-  const struct GNUNET_HashCode *contract_hash);
+  const struct TALER_MERCHANT_HttpResponse *hr);
 
 
 /**
- * Calls the GET /proposal API at the backend.  That is,
- * retrieve a proposal data by providing its transaction id.
+ * Modify an existing instance in the backend.
  *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param order_id order id used to perform the lookup
- * @param nonce nonce to use, only used when requesting the proposal the first 
time,
- *              can be NULL to omit the nonce (after the first request)
- * @param plo_cb callback which will work the response gotten from the backend
- * @param plo_cb_cls closure to pass to @a history_cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_ProposalLookupOperation *
-TALER_MERCHANT_proposal_lookup (
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id identity of the instance to get information about
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param cb function to call with the
+ *        backend's instances information
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstancePatchHandle *
+TALER_MERCHANT_instance_patch (
   struct GNUNET_CURL_Context *ctx,
   const char *backend_url,
-  const char *order_id,
-  const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
-  TALER_MERCHANT_ProposalLookupOperationCallback plo_cb,
-  void *plo_cb_cls);
-
-
-/**
- * Cancel a GET /proposal request.
+  const char *instance_id,
+  unsigned int accounts_length,
+  const char *payto_uris[],
+  const char *name,
+  const json_t *address,
+  const json_t *jurisdiction,
+  const struct TALER_Amount *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const struct TALER_Amount *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  TALER_MERCHANT_InstancePatchCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel /instances request.  Must not be called by clients after
+ * the callback was invoked.
  *
- * @param plo handle to the request to be canceled
+ * @param igh request to cancel.
  */
 void
-TALER_MERCHANT_proposal_lookup_cancel (
-  struct TALER_MERCHANT_ProposalLookupOperation *plo);
-
-
-/* *********************  /pay *********************** */
-
-
-/**
- * @brief Handle to a /pay operation at a merchant.  Note that we use
- * the same handle for interactions with frontends (API for wallets)
- * or backends (API for frontends).  The difference is that for the
- * frontend API, we need the private keys of the coins, while for
- * the backend API we need the public keys and signatures received
- * from the wallet.  Also, the frontend returns a redirect URL on
- * success, while the backend just returns a success status code.
- */
-struct TALER_MERCHANT_Pay;
+TALER_MERCHANT_instance_patch_cancel (
+  struct TALER_MERCHANT_InstancePatchHandle *iph);
 
 
 /**
- * Callbacks of this type are used to serve the result of submitting a
- * /pay request to a merchant.
- *
- * @param cls closure
- * @param hr HTTP response details
+ * Handle for a GET /instances/$ID operation.
  */
-typedef void
-(*TALER_MERCHANT_PayCallback) (void *cls,
-                               const struct TALER_MERCHANT_HttpResponse *hr);
+struct TALER_MERCHANT_InstanceGetHandle;
 
 
 /**
- * Information we need from the wallet for each coin when doing the
- * payment.
+ * Details about a merchant's bank account.
  */
-struct TALER_MERCHANT_PayCoin
+struct TALER_MERCHANT_Account
 {
   /**
-   * Denomination key with which the coin is signed
+   * salt used to compute h_wire
    */
-  struct TALER_DenominationPublicKey denom_pub;
+  struct GNUNET_HashCode salt;
 
   /**
-   * Exchange’s unblinded signature of the coin
+   * payto:// URI of the account.
    */
-  struct TALER_DenominationSignature denom_sig;
+  const char *payto_uri;
 
   /**
-   * Overall value that coins of this @e denom_pub have.
+   * Hash of @e payto_uri and @e salt.
    */
-  struct TALER_Amount denom_value;
+  struct GNUNET_HashCode h_wire;
 
   /**
-   * Coin's private key.
+   * true if the account is active,
+   * false if it is historic.
    */
-  struct TALER_CoinSpendPrivateKeyP coin_priv;
+  bool active;
+};
+
 
+/**
+ * Details about an instance.
+ */
+struct TALER_MERCHANT_InstanceDetails
+{
   /**
-   * Amount this coin contributes to (including fee).
+   * Name of the merchant instance
    */
-  struct TALER_Amount amount_with_fee;
+  const char *name;
 
   /**
-   * Amount this coin contributes to (without fee).
+   * public key of the merchant instance
    */
-  struct TALER_Amount amount_without_fee;
+  const struct TALER_MerchantPublicKeyP *merchant_pub;
 
   /**
-   * Fee the exchange charges for refunds of this coin.
+   * physical address of the merchant instance
    */
-  struct TALER_Amount refund_fee;
+  const json_t *address;
 
   /**
-   * URL of the exchange that issued @e coin_priv.
+   * jurisdiction of the merchant instance
    */
-  const char *exchange_url;
-
-};
-
+  const json_t *jurisdiction;
 
-/**
- * Pay a merchant.  API for wallets that have the coin's private keys.
- *
- * @param ctx execution context
- * @param merchant_url base URL of the merchant
- * @param h_wire hash of the merchant’s account details
- * @param h_contract hash of the contact of the merchant with the customer
- * @param transaction_id transaction id for the transaction between merchant 
and customer
- * @param amount total value of the contract to be paid to the merchant
- * @param max_fee maximum fee covered by the merchant (according to the 
contract)
- * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
- * @param merchant_sig signature from the merchant over the original contract
- * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
- * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
- * @param pay_deadline maximum time limit to pay for this contract
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s 
private key.
- * @param pay_cb the callback to call when a reply for this request is 
available
- * @param pay_cb_cls closure for @a pay_cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
-                           const char *merchant_url,
-                           const struct GNUNET_HashCode *h_contract,
-                           const struct TALER_Amount *amount,
-                           const struct TALER_Amount *max_fee,
-                           const struct TALER_MerchantPublicKeyP *merchant_pub,
-                           const struct TALER_MerchantSignatureP *merchant_sig,
-                           struct GNUNET_TIME_Absolute timestamp,
-                           struct GNUNET_TIME_Absolute refund_deadline,
-                           struct GNUNET_TIME_Absolute pay_deadline,
-                           const struct GNUNET_HashCode *h_wire,
-                           const char *order_id,
-                           unsigned int num_coins,
-                           const struct TALER_MERCHANT_PayCoin *coins,
-                           TALER_MERCHANT_PayCallback pay_cb,
-                           void *pay_cb_cls);
+  /**
+   * default maximum wire fee merchant is willing to fully pay
+   */
+  const struct TALER_Amount *default_max_wire_fee;
 
+  /**
+   * default amortization factor for excess wire fees
+   */
+  uint32_t default_wire_fee_amortization;
 
-/**
- * Entry in the array of refunded coins.
- */
-struct TALER_MERCHANT_RefundEntry
-{
   /**
-   * Merchant signature affirming the refund.
+   * default maximum deposit fee merchant is willing to pay
    */
-  struct TALER_MerchantSignatureP merchant_sig;
+  const struct TALER_Amount *default_max_deposit_fee;
 
   /**
-   * Public key of the refunded coin.
+   * default wire transfer delay merchant will ask for
    */
-  struct TALER_CoinSpendPublicKeyP coin_pub;
+  struct GNUNET_TIME_Relative default_wire_transfer_delay;
 
   /**
-   * Refund transaction ID.
+   * default validity period for offers merchant makes
    */
-  uint64_t rtransaction_id;
+  struct GNUNET_TIME_Relative default_pay_delay;
 };
 
 
 /**
- * Callbacks of this type are used to serve the result of submitting a
- * /pay request to a merchant.
+ * Function called with the result of the GET /instances/$ID operation.
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param merchant_pub public key of the merchant
- * @param h_contract hash of the contract
- * @param num_refunds size of the @a res array, 0 on errors
- * @param res merchant signatures refunding coins, NULL on errors
+ * @param hr HTTP response data
+ * @param accounts_length length of the @a accounts array
+ * @param accounts bank accounts of the merchant instance
+ * @param details details about the instance configuration
  */
 typedef void
-(*TALER_MERCHANT_PayRefundCallback) (
+(*TALER_MERCHANT_InstanceGetCallback)(
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const struct GNUNET_HashCode *h_contract,
-  unsigned int num_refunds,
-  const struct TALER_MERCHANT_RefundEntry *res);
+  unsigned int accounts_length,
+  const struct TALER_MERCHANT_Account accounts[],
+  const struct TALER_MERCHANT_InstanceDetails *details);
 
 
 /**
- * Run a payment abort operation, asking for refunds for coins
- * that were previously spend on a /pay that failed to go through.
+ * Get the details on one of the instances of a backend. Will connect to the
+ * merchant backend and obtain information about the instance.  The respective
+ * information will be passed to the @a cb once available.
  *
- * @param ctx execution context
- * @param merchant_url base URL of the merchant
- * @param h_wire hash of the merchant’s account details
- * @param h_contract hash of the contact of the merchant with the customer
- * @param transaction_id transaction id for the transaction between merchant 
and customer
- * @param amount total value of the contract to be paid to the merchant
- * @param max_fee maximum fee covered by the merchant (according to the 
contract)
- * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
- * @param merchant_sig signature from the merchant over the original contract
- * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
- * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
- * @param pay_deadline maximum time limit to pay for this contract
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s 
private key.
- * @param payref_cb the callback to call when a reply for this request is 
available
- * @param payref_cb_cls closure for @a pay_cb
- * @return a handle for this request
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id identity of the instance to get information about
+ * @param cb function to call with the
+ *        backend's instances information
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
  */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_abort (struct GNUNET_CURL_Context *ctx,
+struct TALER_MERCHANT_InstanceGetHandle *
+TALER_MERCHANT_instance_get (struct GNUNET_CURL_Context *ctx,
+                             const char *backend_url,
+                             const char *instance_id,
+                             TALER_MERCHANT_InstanceGetCallback cb,
+                             void *cb_cls);
+
+
+/**
+ * Cancel /instances request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param igh request to cancel.
+ */
+void
+TALER_MERCHANT_instance_get_cancel (
+  struct TALER_MERCHANT_InstanceGetHandle *igh);
+
+
+/**
+ * Handle for a DELETE /instances operation.
+ */
+struct TALER_MERCHANT_InstanceDeleteHandle;
+
+
+/**
+ * Function called with the result of the DELETE /instances operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_MERCHANT_InstanceDeleteCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Delete the private key of an instance of a backend, thereby disabling the
+ * instance for future requests.  Will preserve the other instance data
+ * (i.e. for taxation).
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id which instance should be deleted
+ * @param instances_cb function to call with the
+ *        backend's return
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstanceDeleteHandle *
+TALER_MERCHANT_instance_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *instance_id,
+  TALER_MERCHANT_InstanceDeleteCallback instances_cb,
+  void *instances_cb_cls);
+
+
+/**
+ * Purge all data associated with an instance. Use with
+ * extreme caution.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id which instance should be deleted
+ * @param instances_cb function to call with the
+ *        backend's return
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstanceDeleteHandle *
+TALER_MERCHANT_instance_purge (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *instance_id,
+  TALER_MERCHANT_InstanceDeleteCallback instances_cb,
+  void *instances_cb_cls);
+
+
+/**
+ * Cancel /instances DELETE request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param idh request to cancel.
+ */
+void
+TALER_MERCHANT_instance_delete_cancel (
+  struct TALER_MERCHANT_InstanceDeleteHandle *idh);
+
+
+/**
+ * Cancel /instances DELETE request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param arg request to cancel.
+ */
+#define TALER_MERCHANT_instance_purge_cancel(arg) \
+  TALER_MERCHANT_instance_delete_cancel (arg)
+
+
+/* ********************* /products *********************** */
+
+
+/**
+ * Handle for a GET /products operation.
+ */
+struct TALER_MERCHANT_ProductsGetHandle;
+
+/**
+ * Individual product from the inventory (minimal information
+ * returned via GET /products).
+ */
+struct TALER_MERCHANT_InventoryEntry
+{
+  /**
+   * Product identifier.
+   */
+  const char *product_id;
+
+};
+
+
+/**
+ * Function called with the result of the GET /products operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param products_length length of the @a products array
+ * @param products array of products the requested instance offers
+ */
+typedef void
+(*TALER_MERCHANT_ProductsGetCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  unsigned int products_length,
+  const struct TALER_MERCHANT_InventoryEntry products[]);
+
+
+/**
+ * Make a GET /products request.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend's inventory information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductsGetHandle *
+TALER_MERCHANT_products_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  TALER_MERCHANT_ProductsGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel GET /products operation.
+ *
+ * @param pgh operation to cancel
+ */
+void
+TALER_MERCHANT_products_get_cancel (
+  struct TALER_MERCHANT_ProductsGetHandle *pgh);
+
+
+/**
+ * Handle for a GET /product/$ID operation. Gets details
+ * about a single product. Do not confused with a
+ * `struct TALER_MERCHANT_ProductsGetHandle`, which
+ * obtains a list of all products.
+ */
+struct TALER_MERCHANT_ProductGetHandle;
+
+
+/**
+ * Function called with the result of the GET /products operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books),
+ *                does NOT indicate remaining stocks, to get remaining stocks,
+ *                subtract @a total_sold and @a total_lost. Note that this 
still
+ *                does not then say how many of the remaining inventory are 
locked.
+ * @param total_sold in @a units, total number of @a unit of product sold
+ * @param total_lost in @a units, total number of @a unit of product lost from 
inventory
+ * @param location where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ */
+typedef void
+(*TALER_MERCHANT_ProductGetCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const char *description,
+  const json_t *description_i18n,
+  const char *unit,
+  const struct TALER_Amount *price,
+  const json_t *image,
+  const json_t *taxes,
+  int64_t total_stock,
+  uint64_t total_sold,
+  uint64_t total_lost,
+  const json_t *location,
+  struct GNUNET_TIME_Absolute next_restock);
+
+
+/**
+ * Make a GET /product/$ID request to get details about an
+ * individual product.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier of the product to inquire about
+ * @param cb function to call with the backend's product information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductGetHandle *
+TALER_MERCHANT_product_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  TALER_MERCHANT_ProductGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel GET /products/$ID operation.
+ *
+ * @param pgh operation to cancel
+ */
+void
+TALER_MERCHANT_product_get_cancel (
+  struct TALER_MERCHANT_ProductGetHandle *pgh);
+
+
+/**
+ * Handle for a POST /products operation.
+ */
+struct TALER_MERCHANT_ProductsPostHandle;
+
+
+/**
+ * Function called with the result of the POST /products operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_ProductsPostCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a POST /products request to add a product to the
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier to use for the product
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books)
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductsPostHandle *
+TALER_MERCHANT_products_post (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  const char *description,
+  const json_t *description_i18n,
+  const char *unit,
+  const struct TALER_Amount *price,
+  const json_t *image,
+  const json_t *taxes,
+  int64_t total_stock,
+  const json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  TALER_MERCHANT_ProductsPostCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel POST /products operation.
+ *
+ * @param pph operation to cancel
+ */
+void
+TALER_MERCHANT_products_post_cancel (
+  struct TALER_MERCHANT_ProductsPostHandle *pph);
+
+
+/**
+ * Handle for a PATCH /products operation.
+ */
+struct TALER_MERCHANT_ProductPatchHandle;
+
+
+/**
+ * Function called with the result of the PATCH /products operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_ProductPatchCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a PATCH /products request to update product details in the
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier to use for the product; the product must exist,
+ *                    or the transaction will fail with a #MHD_HTTP_NOT_FOUND
+ *                    HTTP status code
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books),
+ *               must be larger than previous values
+ * @param total_lost in @a units, must be larger than previous values, and may
+ *               not exceed total_stock minus total_sold; if it does, the 
transaction
+ *               will fail with a #MHD_HTTP_CONFLICT HTTP status code
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductPatchHandle *
+TALER_MERCHANT_product_patch (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  const char *description,
+  const json_t *description_i18n,
+  const char *unit,
+  const struct TALER_Amount *price,
+  const json_t *image,
+  const json_t *taxes,
+  int64_t total_stock,
+  uint64_t total_lost,
+  const json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  TALER_MERCHANT_ProductPatchCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel PATCH /products operation.
+ *
+ * @param pph operation to cancel
+ */
+void
+TALER_MERCHANT_product_patch_cancel (
+  struct TALER_MERCHANT_ProductPatchHandle *pph);
+
+
+/**
+ * Handle for a POST /products/$ID/lock operation.
+ */
+struct TALER_MERCHANT_ProductLockHandle;
+
+
+/**
+ * Function called with the result of the POST /product/$ID/lock operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_ProductLockCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a POST /products/$ID/lock request to reserve a certain
+ * amount of product in inventory to a reservation UUID.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier of the product
+ * @param uuid UUID that identifies the client holding the lock
+ * @param duration how long should the lock be held
+ * @param quantity how much product should be locked
+ * @param cb function to call with the backend's lock status
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductLockHandle *
+TALER_MERCHANT_product_lock (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  const struct GNUNET_Uuid *uuid,
+  struct GNUNET_TIME_Relative duration,
+  uint32_t quantity,
+  TALER_MERCHANT_ProductLockCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel POST /products/$ID/lock operation. Note that the
+ * lock may or may not be acquired despite the cancellation.
+ *
+ * @param pdh operation to cancel
+ */
+void
+TALER_MERCHANT_product_lock_cancel (
+  struct TALER_MERCHANT_ProductLockHandle *plh);
+
+
+/**
+ * Handle for a DELETE /products/$ID operation.
+ */
+struct TALER_MERCHANT_ProductDeleteHandle;
+
+
+/**
+ * Function called with the result of the DELETE /product/$ID operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_ProductDeleteCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a DELETE /products/$ID request to delete a product from our
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier of the product
+ * @param cb function to call with the backend's deletion status
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductDeleteHandle *
+TALER_MERCHANT_product_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  TALER_MERCHANT_ProductDeleteCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel DELETE /products/$ID operation.
+ *
+ * @param pdh operation to cancel
+ */
+void
+TALER_MERCHANT_product_delete_cancel (
+  struct TALER_MERCHANT_ProductDeleteHandle *pdh);
+
+
+/* ********************* /orders ************************** */
+
+
+/**
+ * Handle to a POST /orders operation
+ */
+// FIXME: rename: Operation => Handle!
+struct TALER_MERCHANT_PostOrdersOperation;
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /orders request to a merchant.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param order_id order id of the newly created order
+ */
+typedef void
+(*TALER_MERCHANT_PostOrdersCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const char *order_id);
+
+
+/**
+ * POST to /orders at the backend to setup an order and obtain
+ * the order ID (which may have been set by the front-end).
+ *
+ * @param ctx execution context
+ * @param backend_url URL of the backend
+ * @param order basic information about this purchase, to be extended by the 
backend
+ * @param refund_delay how long can refunds happen for this order; 0 to use
+ *             absolute value from contract (or not allow refunds).
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request, NULL on error
+ */
+struct TALER_MERCHANT_PostOrdersOperation *
+TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx,
+                            const char *backend_url,
+                            const json_t *order,
+                            struct GNUNET_TIME_Relative refund_delay,
+                            TALER_MERCHANT_PostOrdersCallback cb,
+                            void *cb_cls);
+
+/**
+ * Information needed per product for constructing orders from
+ * the inventory.
+ */
+struct TALER_MERCHANT_InventoryProduct
+{
+
+  /**
+   * Identifier of the product.
+   */
+  char *product_id;
+
+  /**
+   * How many units of this product should be ordered.
+   */
+  unsigned int quantity;
+};
+
+
+/**
+ * POST to /orders at the backend to setup an order and obtain
+ * the order ID (which may have been set by the front-end).
+ *
+ * @param ctx execution context
+ * @param backend_url URL of the backend
+ * @param order basic information about this purchase, to be extended by the 
backend
+ * @param refund_delay how long can refunds happen for this order; 0 to use
+ *             absolute value from contract (or not allow refunds).
+ * @param payment_target desired payment target identifier (to select merchant 
bank details)
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products products to add to the order from the inventory
+ * @param lock_uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs with locks on @a inventory_products
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request, NULL on error
+ */
+struct TALER_MERCHANT_PostOrdersOperation *
+TALER_MERCHANT_orders_post2 (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const json_t *order,
+  struct GNUNET_TIME_Relative refund_delay,
+  const char *payment_target,
+  unsigned int inventory_products_length,
+  const struct TALER_MERCHANT_InventoryProduct inventory_products[],
+  unsigned int uuids_length,
+  const struct GNUNET_Uuid uuids[],
+  TALER_MERCHANT_PostOrdersCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a POST /orders request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param po the proposal operation request handle
+ */
+void
+TALER_MERCHANT_orders_post_cancel (
+  struct TALER_MERCHANT_PostOrdersOperation *po);
+
+
+/**
+ * Handle for a GET /orders operation.
+ */
+struct TALER_MERCHANT_OrdersGetHandle;
+
+/**
+ * Individual order (minimal information returned via GET /orders).
+ */
+struct TALER_MERCHANT_OrderEntry
+{
+  /**
+   * Order identifier.
+   */
+  const char *order_id;
+
+  /**
+   * Time when the order was created. Useful for filtering by
+   * 'date' (in #TALER_MERCHANT_orders_get2()).
+   */
+  struct GNUNET_TIME_Absolute timestamp;
+
+  /**
+   * Serial ID of the order. Useful for filtering by 'start_row'
+   * (in #TALER_MERCHANT_orders_get2()).
+   */
+  uint64_t order_serial;
+
+};
+
+
+/**
+ * Function called with the result of the GET /orders operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param orders_length length of the @a orders array
+ * @param orders array of orders the requested instance has made
+ */
+typedef void
+(*TALER_MERCHANT_OrdersGetCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  unsigned int orders_length,
+  const struct TALER_MERCHANT_OrderEntry orders[]);
+
+
+/**
+ * Make a GET /orders request.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend's inventory information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_OrdersGetHandle *
+TALER_MERCHANT_orders_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  TALER_MERCHANT_OrdersGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Make a GET /orders request with filters.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param paid filter on payment status
+ * @param refunded filter on refund status
+ * @param wired filter on wire transfer status
+ * @param date range limit by date
+ * @param start_row range limit by order table row
+ * @param delta range from which @a date and @a start_row apply, positive
+ *              to return delta items after the given limit(s), negative to
+ *              return delta items before the given limit(s)
+ * @param timeout how long to wait (long polling) of zero results match the 
query
+ * @param cb function to call with the backend's inventory information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_OrdersGetHandle *
+TALER_MERCHANT_orders_get2 (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  enum TALER_EXCHANGE_YesNoAll paid,
+  enum TALER_EXCHANGE_YesNoAll refunded,
+  enum TALER_EXCHANGE_YesNoAll wired,
+  struct GNUNET_TIME_Absolute date,
+  uint64_t start_row,
+  int64_t delta,
+  struct GNUNET_TIME_Relative timeout,
+  TALER_MERCHANT_OrdersGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel GET /orders operation.
+ *
+ * @param pgh operation to cancel
+ */
+void
+TALER_MERCHANT_orders_get_cancel (
+  struct TALER_MERCHANT_OrdersGetHandle *pgh);
+
+
+/**
+ * Handle for a GET /orders/$ID operation. (Wallet's public endpoint,
+ * not to be confused with the private endpoint for the merchant.)
+ */
+struct TALER_MERCHANT_OrderWalletGetHandle;
+
+
+/**
+ * Detail about a refund lookup result.
+ */
+struct TALER_MERCHANT_RefundDetail
+{
+
+  /**
+   * Exchange response details.  Full details are only included
+   * upon failure (HTTP status is not #MHD_HTTP_OK).
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Coin this detail is about.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Refund transaction ID used.
+   */
+  uint64_t rtransaction_id;
+
+  /**
+   * Amount to be refunded for this coin.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Public key of the exchange affirming the refund,
+   * only valid if the @e hr http_status is #MHD_HTTP_OK.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Signature of the exchange affirming the refund,
+   * only valid if the @e hr http_status is #MHD_HTTP_OK.
+   */
+  struct TALER_ExchangeSignatureP exchange_sig;
+
+};
+
+
+/**
+ * Callback to process a GET /orders/$ID request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not
+ *        settled, #GNUNET_SYSERR on error
+ *        (note that refunded payments are returned as paid!)
+ * @param refunded #GNUNET_YES if there is at least on refund on this payment,
+ *        #GNUNET_NO if refunded, #GNUNET_SYSERR or error
+ * @param refunded_amount amount that was refunded, NULL if there
+ *        was no refund
+ * @param taler_pay_uri the URI that instructs the wallets to process
+ *                      the payment
+ * @param already_paid_order_id equivalent order that this customer
+ *                      paid already, or NULL for none
+ * @param merchant_pub public key of the merchant
+ * @param num_refunds length of the @a refunds array
+ * @param refunds details about the refund processing
+ */
+typedef void
+(*TALER_MERCHANT_OrderWalletGetCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  enum GNUNET_GenericReturnValue paid,
+  enum GNUNET_GenericReturnValue refunded,
+  struct TALER_Amount *refund_amount,
+  const char *taler_pay_uri,
+  const char *already_paid_order_id,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  unsigned int num_refunds,
+  const struct TALER_MERCHANT_RefundDetail *refunds);
+
+
+/**
+ * Checks the status of a payment.  Issue a GET /orders/$ID request to the
+ * backend.  The @a h_contract serves as identification of the wallet and is
+ * used to authorize the request.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param order_id order id to identify the payment
+ * @param h_contract hash of the contract to authenticate the wallet
+ * @param timeout timeout to use in long polling (how long may the server wait 
to reply
+ *        before generating an unpaid response). Note that this is just 
provided to
+ *        the server, we as client will block until the response comes back or 
until
+ *        #TALER_MERCHANT_order_get_cancel() is called.
+ * @param session_id for which session should the payment status be checked. 
Use
+ *        NULL to disregard sessions.
+ * @param min_refund long poll for the service to approve a refund exceeding 
this value;
+ *        use NULL to not wait for any refund (only for payment). Only makes 
sense
+ *        with a non-zero @a timeout. Can be NULL.
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_OrderWalletGetHandle *
+TALER_MERCHANT_wallet_order_get (struct GNUNET_CURL_Context *ctx,
+                                 const char *backend_url,
+                                 const char *order_id,
+                                 const struct GNUNET_HashCode *h_contract,
+                                 struct GNUNET_TIME_Relative timeout,
+                                 const char *session_id,
+                                 const struct TALER_Amount *min_refund,
+                                 TALER_MERCHANT_OrderWalletGetCallback cb,
+                                 void *cb_cls);
+
+/**
+ * Cancel a GET /orders/$ID request.
+ *
+ * @param owgh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_wallet_order_get_cancel (
+  struct TALER_MERCHANT_OrderWalletGetHandle *owgh);
+
+
+/**
+ * Handle for a GET /private/orders/$ID operation (merchant's internal
+ * API, not to be confused with the endpoint for wallets).
+ */
+struct TALER_MERCHANT_OrderMerchantGetHandle;
+
+
+/**
+ * Details about a wire transfer to the merchant related to the order.
+ */
+struct TALER_MERCHANT_WireTransfer
+{
+  /**
+   * Base URL of the exchange that made the transfer.
+   */
+  const char *exchange_url;
+
+  /**
+   * When the transfer took place (note: may not be exact,
+   * as the time at which the exchange initiated the transfer
+   * may differ from the time the bank or the merchant observed).
+   */
+  struct GNUNET_TIME_Absolute execution_time;
+
+  /**
+   * Wire transfer subject.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+  /**
+   * Total amount that was transferred.
+   */
+  struct TALER_Amount total_amount;
+
+  /**
+   * Whether this transfer was confirmed by the merchant using
+   * the POST /transfers API, or whether it was merely claimed
+   * by the exchange.
+   */
+  bool confirmed;
+};
+
+
+/**
+ * Details about a failures returned by the exchange when
+ * tracking wire transfers.
+ */
+struct TALER_MERCHANT_WireReport
+{
+
+  /**
+   * Error code explaining the nature of the problem.
+   */
+  enum TALER_ErrorCode code;
+
+  /**
+   * Human-readable error description.
+   */
+  const char *hint;
+
+  /**
+   * Public key of the coin involved.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * HTTP response data from the exchange (fields are MAY BE NULL or 0 if not
+   * available).
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Detail returned by the merchant backend about refunds.
+ */
+struct TALER_MERCHANT_RefundOrderDetail
+{
+
+  /**
+   * Human-readable reason given for the refund.
+   */
+  const char *reason;
+
+  /**
+   * Time when the refund was granted.
+   */
+  struct GNUNET_TIME_Absolute refund_time;
+
+  /**
+   * Total amount that was refunded.
+   */
+  struct TALER_Amount refund_amount;
+
+};
+
+
+/**
+ * Details about the status of an order.
+ */
+struct TALER_MERCHANT_OrderStatusResponse
+{
+
+  /**
+   * true if the payment is settled, false if not settled.
+   */
+  bool paid;
+
+  /**
+   * Details depending on the payment status given in @e paid.
+   */
+  union
+  {
+
+    /**
+     * Details provided if @e paid is #GNUNET_YES.
+     */
+    struct
+    {
+      /**
+       * Amount that was refunded.
+       */
+      struct TALER_Amount refund_amount;
+
+      /**
+       * Amount that was deposited into our bank account,
+       * excluding fees.
+       */
+      struct TALER_Amount deposit_total;
+
+      /**
+       * The full contract terms of the order.
+       */
+      const json_t *contract_terms;
+
+      /**
+       * Array of wire transfers made for this payment to the
+       * merchant by the exchange. Of length @e wts_len.
+       */
+      struct TALER_MERCHANT_WireTransfer *wts;
+
+      /**
+       * Length of the @e wts array.
+       */
+      unsigned int wts_len;
+
+      /**
+       * Array of wire reports about problems tracking wire transfers.
+       * Of length @e wrs_len.
+       */
+      struct TALER_MERCHANT_WireReport *wrs;
+
+      /**
+       * Length of the @e wrs array.
+       */
+      unsigned int wrs_len;
+
+      /**
+       * Details returned by the merchant backend about refunds.
+       * Of length @e refunds_len.
+       */
+      struct TALER_MERCHANT_RefundOrderDetail *refunds;
+
+      /**
+       * Length of the @e refunds array.
+       */
+      unsigned int refunds_len;
+
+      /**
+       * Error code encountered trying to contact the exchange
+       * about the wire tracking, 0 for none.
+       */
+      enum TALER_ErrorCode exchange_ec;
+
+      /**
+       * HTTP status code encountered trying to contact the exchange
+       * about the wire tracking, 0 for no error.
+       */
+      unsigned int exchange_hc;
+
+      /**
+       * true if there is at least on refund on this payment,
+       * false if there are no refunds.
+       */
+      bool refunded;
+
+      /**
+       * true if the exchange paid the merchant for this order,
+       * false if not.
+       */
+      bool wired;
+
+    } paid;
+
+    /**
+     * Details provided if @e paid is #GNUNET_NO.
+     */
+    struct
+    {
+
+      /**
+       * URI that should be shown to the wallet to trigger a payment.
+       */
+      const char *taler_pay_uri;
+
+      /**
+       * Alternative order ID which was paid for already in the same session.
+       * Only given if the same product was purchased before in the same 
session.
+       * Otherwise NULL.
+       */
+      const char *already_paid_order_id;
+
+    } unpaid;
+
+  } details;
+};
+
+
+/**
+ * Callback to process a GET /orders/$ID request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param osr order status response details (on success)
+ */
+typedef void
+(*TALER_MERCHANT_OrderMerchantGetCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct TALER_MERCHANT_OrderStatusResponse *osr);
+
+
+/**
+ * Checks the status of a payment.  Issue a GET /private/orders/$ID request to
+ * the backend.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param order_id order id to identify the payment
+ * @param session_id sesion id for the payment (or NULL if the check is not
+ *                   bound to a session)
+ * @param transfer if true, obtain the wire transfer status from the exhcange.
+ *               Otherwise, the wire transfer status MAY be returned if it is 
available.
+ * @param timeout timeout to use in long polling (how long may the server wait 
to reply
+ *        before generating an unpaid response). Note that this is just 
provided to
+ *        the server, we as client will block until the response comes back or 
until
+ *        #TALER_MERCHANT_order_get_cancel() is called.
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_OrderMerchantGetHandle *
+TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
+                                   const char *backend_url,
+                                   const char *order_id,
+                                   const char *session_id,
+                                   bool transfer,
+                                   struct GNUNET_TIME_Relative timeout,
+                                   TALER_MERCHANT_OrderMerchantGetCallback cb,
+                                   void *cb_cls);
+
+
+/**
+ * Cancel a GET /private/orders/$ID request.
+ *
+ * @param omgh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_merchant_order_get_cancel (
+  struct TALER_MERCHANT_OrderMerchantGetHandle *omgh);
+
+
+/**
+ * Handle for a DELETE /orders/$ID operation.
+ */
+struct TALER_MERCHANT_OrderDeleteHandle;
+
+
+/**
+ * Function called with the result of the DELETE /order/$ID operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_OrderDeleteCallback)(
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Make a DELETE /orders/$ID request to delete a order from our
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param order_id identifier of the order
+ * @param cb function to call with the backend's deletion status
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_OrderDeleteHandle *
+TALER_MERCHANT_order_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *order_id,
+  TALER_MERCHANT_OrderDeleteCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel DELETE /orders/$ID operation.
+ *
+ * @param odh operation to cancel
+ */
+void
+TALER_MERCHANT_order_delete_cancel (
+  struct TALER_MERCHANT_OrderDeleteHandle *odh);
+
+
+/**
+ * Handle to a POST /orders/$ID/claim handle
+ */
+struct TALER_MERCHANT_OrderClaimHandle;
+
+
+/**
+ * Callback called to process a POST /orders/$ID/claim response.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param contract_terms the details of the contract
+ * @param sig merchant's signature over @a contract_terms (already verified)
+ * @param h_contract_terms hash over @a contract_terms (computed
+ *        client-side to verify @a sig)
+ */
+typedef void
+(*TALER_MERCHANT_OrderClaimCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const json_t *contract_terms,
+  const struct TALER_MerchantSignatureP *sig,
+  const struct GNUNET_HashCode *h_contract_terms);
+
+
+/**
+ * Calls the POST /orders/$ID/claim API at the backend.  That is,
+ * retrieve the final contract terms including the client nonce.
+ *
+ * This is a PUBLIC API for wallets.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param order_id order id used to perform the lookup
+ * @param nonce nonce to use to claim the proposal
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to @a cb
+ * @return handle for this handle, NULL upon errors
+ */
+struct TALER_MERCHANT_OrderClaimHandle *
+TALER_MERCHANT_order_claim (struct GNUNET_CURL_Context *ctx,
+                            const char *backend_url,
+                            const char *order_id,
+                            const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
+                            TALER_MERCHANT_OrderClaimCallback cb,
+                            void *cb_cls);
+
+
+/**
+ * Cancel a POST /order/$ID/claim request.
+ *
+ * @param och handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle 
*och);
+
+
+/**
+ * @brief Handle to a POST /orders/$ID/pay operation at a merchant.  Note that
+ * we use the same handle for interactions with frontends (API for wallets) or
+ * backends (API for frontends).  The difference is that for the frontend API,
+ * we need the private keys of the coins, while for the backend API we need
+ * the public keys and signatures received from the wallet.
+ */
+struct TALER_MERCHANT_OrderPayHandle;
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /orders/$ID/pay request to a merchant.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ */
+typedef void
+(*TALER_MERCHANT_OrderPayCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Information we need from the frontend (ok, the frontend sends just JSON)
+ * when forwarding a payment to the backend.
+ */
+struct TALER_MERCHANT_PaidCoin
+{
+  /**
+   * Denomination key with which the coin is signed
+   */
+  struct TALER_DenominationPublicKey denom_pub;
+
+  /**
+   * Exchange’s unblinded signature of the coin
+   */
+  struct TALER_DenominationSignature denom_sig;
+
+  /**
+   * Overall value that coins of this @e denom_pub have.
+   */
+  struct TALER_Amount denom_value;
+
+  /**
+   * Coin's public key.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Coin's signature key.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Amount this coin contributes to (including fee).
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Amount this coin contributes to (without fee).
+   */
+  struct TALER_Amount amount_without_fee;
+
+  /**
+   * What is the URL of the exchange that issued @a coin_pub?
+   */
+  const char *exchange_url;
+
+};
+
+
+/**
+ * Pay a merchant.  API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures.  Note the sublte difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ *
+ * This is a PUBLIC API, albeit in this form useful for the frontend,
+ * in case the frontend is proxying the request.
+ *
+ * @param ctx execution context
+ * @param merchant_url base URL of the merchant
+ * @param order_id which order should be paid
+ * @param session_id session to pay for, or NULL for none
+ * @param num_coins length of the @a coins array
+ * @param coins array of coins to pay with
+ * @param pay_cb the callback to call when a reply for this request is 
available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay_frontend (
+  struct GNUNET_CURL_Context *ctx,
+  const char *merchant_url,
+  const char *order_id,
+  const char *session_id,
+  unsigned int num_coins,
+  const struct TALER_MERCHANT_PaidCoin coins[],
+  TALER_MERCHANT_OrderPayCallback pay_cb,
+  void *pay_cb_cls);
+
+
+/**
+ * Information we need from the wallet for each coin when doing the
+ * payment.
+ */
+struct TALER_MERCHANT_PayCoin
+{
+  /**
+   * Denomination key with which the coin is signed
+   */
+  struct TALER_DenominationPublicKey denom_pub;
+
+  /**
+   * Exchange’s unblinded signature of the coin
+   */
+  struct TALER_DenominationSignature denom_sig;
+
+  /**
+   * Overall value that coins of this @e denom_pub have.
+   */
+  struct TALER_Amount denom_value;
+
+  /**
+   * Coin's private key.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Amount this coin contributes to (including fee).
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Amount this coin contributes to (without fee).
+   */
+  struct TALER_Amount amount_without_fee;
+
+  /**
+   * URL of the exchange that issued @e coin_priv.
+   */
+  const char *exchange_url;
+
+};
+
+
+/**
+ * Pay a merchant.  API for wallets that have the coin's private keys.
+ *
+ * This is a PUBLIC API for wallets.
+ *
+ * @param ctx execution context
+ * @param merchant_url base URL of the merchant
+ * @param session_id session to pay for, or NULL for none
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param transaction_id transaction id for the transaction between merchant 
and customer
+ * @param amount total value of the contract to be paid to the merchant
+ * @param max_fee maximum fee covered by the merchant (according to the 
contract)
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param merchant_sig signature from the merchant over the original contract
+ * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
+ * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
+ * @param pay_deadline maximum time limit to pay for this contract
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s 
private key.
+ * @param pay_cb the callback to call when a reply for this request is 
available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay (struct GNUNET_CURL_Context *ctx,
                           const char *merchant_url,
+                          const char *session_id,
                           const struct GNUNET_HashCode *h_contract,
                           const struct TALER_Amount *amount,
                           const struct TALER_Amount *max_fee,
@@ -747,120 +2003,199 @@ TALER_MERCHANT_pay_abort (struct GNUNET_CURL_Context 
*ctx,
                           const struct GNUNET_HashCode *h_wire,
                           const char *order_id,
                           unsigned int num_coins,
-                          const struct TALER_MERCHANT_PayCoin *coins,
-                          TALER_MERCHANT_PayRefundCallback payref_cb,
-                          void *payref_cb_cls);
+                          const struct TALER_MERCHANT_PayCoin coins[],
+                          TALER_MERCHANT_OrderPayCallback pay_cb,
+                          void *pay_cb_cls);
 
 
 /**
- * Information we need from the frontend (ok, the frontend sends just JSON)
- * when forwarding a payment to the backend.
+ * Cancel a POST /orders/$ID/pay request.  Note that if you cancel a request
+ * like this, you have no assurance that the request has not yet been
+ * forwarded to the merchant. Thus, the payment may still succeed or fail.
+ * Re-issue the original /pay request to resume/retry and obtain a definitive
+ * result, or refresh the coins involved to ensure that the merchant can no
+ * longer complete the payment.
+ *
+ * @param oph the payment request handle
  */
-struct TALER_MERCHANT_PaidCoin
+void
+TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph);
+
+
+/**
+ * Handle for an POST /orders/$ID/abort operation.
+ */
+struct TALER_MERCHANT_OrderAbortHandle;
+
+
+/**
+ * Entry in the array of refunded coins.
+ */
+struct TALER_MERCHANT_AbortedCoin
 {
   /**
-   * Denomination key with which the coin is signed
+   * Exchange signature affirming the refund.
    */
-  struct TALER_DenominationPublicKey denom_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
 
   /**
-   * Exchange’s unblinded signature of the coin
+   * Exchange public key affirming the refund.
    */
-  struct TALER_DenominationSignature denom_sig;
+  struct TALER_ExchangePublicKeyP exchange_pub;
 
   /**
-   * Overall value that coins of this @e denom_pub have.
+   * Refund fee charged by the exchange. The API will have checked the
+   * signature, but NOT that this is the expected fee.
    */
-  struct TALER_Amount denom_value;
+  struct TALER_Amount refund_fee;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * /orders/$ID/abort request to a merchant.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param merchant_pub public key of the merchant
+ * @param num_aborts size of the @a res array, 0 on errors
+ * @param aborts merchant signatures refunding coins, NULL on errors
+ */
+typedef void
+(*TALER_MERCHANT_AbortCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  unsigned int num_aborts,
+  const struct TALER_MERCHANT_AbortedCoin aborts[]);
+
+
+/**
+ * Information we need from the wallet for each coin when aborting.
+ */
+struct TALER_MERCHANT_AbortCoin
+{
 
   /**
    * Coin's public key.
    */
   struct TALER_CoinSpendPublicKeyP coin_pub;
 
-  /**
-   * Coin's signature key.
-   */
-  struct TALER_CoinSpendSignatureP coin_sig;
-
   /**
    * Amount this coin contributes to (including fee).
    */
   struct TALER_Amount amount_with_fee;
 
   /**
-   * Amount this coin contributes to (without fee).
-   */
-  struct TALER_Amount amount_without_fee;
-
-  /**
-   * Fee the exchange charges for refunds of this coin.
-   */
-  struct TALER_Amount refund_fee;
-
-  /**
-   * What is the URL of the exchange that issued @a coin_pub?
+   * URL of the exchange that issued @e coin_priv.
    */
   const char *exchange_url;
 
 };
 
-
 /**
- * Pay a merchant.  API for frontends talking to backends. Here,
- * the frontend does not have the coin's private keys, but just
- * the public keys and signatures.  Note the sublte difference
- * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ * Run a payment abort operation, asking for the payment to be aborted,
+ * yieldingrefunds for coins that were previously spend on a payment that
+ * failed to go through.
+ *
+ * This is a PUBLIC API for wallets.
  *
  * @param ctx execution context
  * @param merchant_url base URL of the merchant
- * @param merchant_pub public key of the merchant
- * @param order_id which order should be paid
- * @param num_coins length of the @a coins array
- * @param coins array of coins to pay with
- * @param pay_cb the callback to call when a reply for this request is 
available
- * @param pay_cb_cls closure for @a pay_cb
+ * @param order_id order to abort
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
  * @return a handle for this request
  */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_frontend (
-  struct GNUNET_CURL_Context *ctx,
-  const char *merchant_url,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const char *order_id,
-  unsigned int num_coins,
-  const struct TALER_MERCHANT_PaidCoin *coins,
-  TALER_MERCHANT_PayCallback pay_cb,
-  void *pay_cb_cls);
+struct TALER_MERCHANT_OrderAbortHandle *
+TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
+                            const char *merchant_url,
+                            const char *order_id,
+                            const struct TALER_MerchantPublicKeyP 
*merchant_pub,
+                            const struct GNUNET_HashCode *h_contract,
+                            unsigned int num_coins,
+                            const struct TALER_MERCHANT_AbortCoin coins[],
+                            TALER_MERCHANT_AbortCallback cb,
+                            void *cb_cls);
+
+
+/**
+ * Cancel a POST /orders/$ID/abort request.  Note that if you cancel a request
+ * like this, you have no assurance that the request has not yet been
+ * forwarded to the merchant.
+ *
+ * @param oah the abort request handle
+ */
+void
+TALER_MERCHANT_order_abort_cancel (struct TALER_MERCHANT_OrderAbortHandle 
*oah);
+
+
+/**
+ * Handle for a POST /orders/ID/refund operation.
+ */
+struct TALER_MERCHANT_OrderRefundHandle;
+
+
+/**
+ * Callback to process a POST /orders/ID/refund request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec taler-specific error code
+ */
+typedef void
+(*TALER_MERCHANT_RefundCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
 
+/**
+ * Increase the refund granted for an order
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param backend_url backend's base URL, including final "/"
+ * @param order_id id of the order whose refund is to be increased
+ * @param refund amount to which increase the refund
+ * @param reason human-readable reason justifying the refund
+ * @param cb callback processing the response from /refund
+ * @param cb_cls closure for cb
+ */
+struct TALER_MERCHANT_OrderRefundHandle *
+TALER_MERCHANT_post_order_refund (struct GNUNET_CURL_Context *ctx,
+                                  const char *backend_url,
+                                  const char *order_id,
+                                  const struct TALER_Amount *refund,
+                                  const char *reason,
+                                  TALER_MERCHANT_RefundCallback cb,
+                                  void *cb_cls);
 
 /**
- * Cancel a /pay request.  Note that if you cancel a request like
- * this, you have no assurance that the request has not yet been
- * forwarded to the merchant. Thus, the payment may still succeed or
- * fail.  Re-issue the original /pay request to resume/retry and
- * obtain a definitive result, or /refresh the coins involved to
- * ensure that the merchant can no longer complete the payment.
+ * Cancel a POST /refund request.
  *
- * @param ph the payment request handle
+ * @param rio the refund increasing operation to cancel
  */
 void
-TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *ph);
+TALER_MERCHANT_post_order_refund_cancel (
+  struct TALER_MERCHANT_OrderRefundHandle *orh);
 
 
-/* ********************* /track/transfer *********************** */
+/* ********************* /transfers *********************** */
 
 /**
- * @brief Handle to a /track/transfer operation at a merchant's backend.
+ * @brief Handle to a POST /transfers operation at a merchant's backend.
  */
-struct TALER_MERCHANT_TrackTransferHandle;
+struct TALER_MERCHANT_PostTransfersHandle;
 
 /**
  * Information about the _total_ amount that was paid back
  * by the exchange for a given h_contract_terms, by _one_ wire
  * transfer.
  */
-struct TALER_MERCHANT_TrackTransferDetails
+struct TALER_MERCHANT_TrackTransferDetail
 {
 
   /**
@@ -881,235 +2216,463 @@ struct TALER_MERCHANT_TrackTransferDetails
 };
 
 /**
- * Callbacks of this type are used to work the result of submitting a 
/track/transfer request to a merchant
+ * Callbacks of this type are used to work the result of submitting a
+ * POST /transfers request to a merchant
  *
  * @param cls closure
  * @param hr HTTP response details
- * @param sign_key exchange key used to sign @a json, or NULL
- * @param h_wire hash of the wire transfer address the transfer went to, or 
NULL on error
- * @param total_amount total amount of the wire transfer, or NULL if the 
exchange could
- *             not provide any @a wtid (set only if @a http_status is 
#MHD_HTTP_OK)
+ * @param execution_time when did the transfer happen (according to the 
exchange),
+ *          #GNUNET_TIME_UNIT_FOREVER_ABS if the transfer did not yet happen 
or if
+ *          we have no data from the exchange about it
+ * @param total_amount total amount of the wire transfer, or NULL if the 
exchange did
+ *             not provide any details
+ * @param wire_fee how much did the exchange charge in terms of wire fees, or 
NULL
+ *             if the exchange did not provide any details
  * @param details_length length of the @a details array
  * @param details array with details about the combined transactions
  */
 typedef void
-(*TALER_MERCHANT_TrackTransferCallback) (
+(*TALER_MERCHANT_PostTransfersCallback) (
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
-  const struct TALER_ExchangePublicKeyP *sign_key,
-  const struct GNUNET_HashCode *h_wire,
+  struct GNUNET_TIME_Absolute execution_time,
   const struct TALER_Amount *total_amount,
+  const struct TALER_Amount *wire_fee,
   unsigned int details_length,
-  const struct TALER_MERCHANT_TrackTransferDetails *details);
+  const struct TALER_MERCHANT_TrackTransferDetail details[]);
 
 
 /**
- * Request backend to return deposits associated with a given wtid.
+ * Request backend to remember that we received the given
+ * wire transfer and to request details about the aggregated
+ * transactions from the exchange.
  *
  * @param ctx execution context
  * @param backend_url base URL of the backend
- * @param wire_method wire method used for the wire transfer
+ * @param credit_amount how much money did we receive (without wire fee)
  * @param wtid base32 string indicating a wtid
- * @param exchange base URL of the exchange in charge of returning the wanted 
information
- * @param track_transfer_cb the callback to call when a reply for this request 
is available
- * @param track_transfer_cb_cls closure for @a track_transfer_cb
+ * @param payto_uri which account was credited by the wire transfer
+ * @param exchange_url what is the URL of the exchange that made the transfer
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
  * @return a handle for this request
  */
-struct TALER_MERCHANT_TrackTransferHandle *
-TALER_MERCHANT_track_transfer (
+struct TALER_MERCHANT_PostTransfersHandle *
+TALER_MERCHANT_transfers_post (
   struct GNUNET_CURL_Context *ctx,
   const char *backend_url,
-  const char *wire_method,
+  const struct TALER_Amount *credit_amount,
   const struct TALER_WireTransferIdentifierRawP *wtid,
+  const char *payto_uri,
   const char *exchange_url,
-  TALER_MERCHANT_TrackTransferCallback
-  track_transfer_cb,
-  void *track_transfer_cb_cls);
+  TALER_MERCHANT_PostTransfersCallback cb,
+  void *cls);
 
 
 /**
- * Cancel a /track/transfer request.  This function cannot be used
+ * Cancel a POST /transfers request.  This function cannot be used
  * on a request handle if a response is already served for it.
  *
- * @param co the deposit's tracking operation
+ * @param pth the operation to cancel
  */
 void
-TALER_MERCHANT_track_transfer_cancel (
-  struct TALER_MERCHANT_TrackTransferHandle *tdo);
+TALER_MERCHANT_transfers_post_cancel (
+  struct TALER_MERCHANT_PostTransfersHandle *pth);
 
 
-/* ********************* /track/transaction *********************** */
-
 /**
- * @brief Handle to a /track/transaction operation at a merchant's backend.
+ * @brief Handle to a GET /transfers operation at a merchant's backend.
  */
-struct TALER_MERCHANT_TrackTransactionHandle;
+struct TALER_MERCHANT_GetTransfersHandle;
 
 /**
- * Information about a coin aggregated in a wire transfer for a
- * /track/transaction response.
+ * Information about the _total_ amount that was paid back
+ * by the exchange for a given h_contract_terms, by _one_ wire
+ * transfer.
  */
-struct TALER_MERCHANT_CoinWireTransfer
+struct TALER_MERCHANT_TransferData
 {
 
   /**
-   * Public key of the coin.
+   * Total amount wired by the exchange (without wire fee)
    */
-  struct TALER_CoinSpendPublicKeyP coin_pub;
+  struct TALER_Amount credit_amount;
 
   /**
-   * Value of the coin including deposit fee.
+   * Wire transfer identifier used.
    */
-  struct TALER_Amount amount_with_fee;
+  struct TALER_WireTransferIdentifierRawP wtid;
 
   /**
-   * Deposit fee for the coin.
+   * URI of the target account.
    */
-  struct TALER_Amount deposit_fee;
+  const char *payto_uri;
+
+  /**
+   * URL of the exchange that made the transfer.
+   */
+  const char *exchange_url;
+
+  /**
+   * Serial number of the credit operation in the merchant backend.
+   */
+  uint64_t credit_serial;
+
+  /**
+   * Time of the wire transfer, according to the exchange.
+   */
+  struct GNUNET_TIME_Absolute execution_time;
+
+  /**
+   * Did we check the exchange's answer and are happy about it?  False
+   * if we did not check or are unhappy with the answer.
+   */
+  bool verified;
+
+  /**
+   * Did we confirm the wire transfer happened (via 
#TALER_MERCHANT_transfers_post())?
+   */
+  bool confirmed;
 
 };
 
+/**
+ * Callbacks of this type are used to work the result of submitting a
+ * GET /transfers request to a merchant
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param transfers_length length of the @a transfers array
+ * @param transfers array with details about the transfers we received
+ */
+typedef void
+(*TALER_MERCHANT_GetTransfersCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  unsigned int transfers_length,
+  const struct TALER_MERCHANT_TransferData transfers[]);
+
+
+/**
+ * Request backend to return list of all wire transfers that
+ * we received (or that the exchange claims we should have received).
+ *
+ * Note that when filtering by timestamp (using “before” and/or “after”), we
+ * use the time reported by the exchange and thus will ONLY return results for
+ * which we already have a response from the exchange. This should be
+ * virtually all transfers, however it is conceivable that for some transfer
+ * the exchange responded with a temporary error (i.e. HTTP status 500+) and
+ * then we do not yet have an execution time to filter by. Thus, IF timestamp
+ * filters are given, transfers for which we have no response from the
+ * exchange yet are automatically excluded.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the backend
+ * @param payto_uri filter by this credit account of the merchant
+ * @param before limit to transactions before this timestamp, use
+ *                 #GNUNET_TIME_UNIT_FOREVER_ABS to not filter by @a before
+ * @param after limit to transactions after this timestamp, use
+ *                 #GNUNET_TIME_UNIT_ZERO_ABS to not filter by @a after
+ * @param limit return at most ths number of results; negative to descend in 
execution time
+ * @param offset start at this "credit serial" number (exclusive)
+ * @param verified filter results by verification status
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_GetTransfersHandle *
+TALER_MERCHANT_transfers_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *payto_uri,
+  const struct GNUNET_TIME_Absolute before,
+  const struct GNUNET_TIME_Absolute after,
+  int64_t limit,
+  uint64_t offset,
+  enum TALER_EXCHANGE_YesNoAll verified,
+  TALER_MERCHANT_GetTransfersCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a POST /transfers request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param pth the operation to cancel
+ */
+void
+TALER_MERCHANT_transfers_get_cancel (
+  struct TALER_MERCHANT_GetTransfersHandle *gth);
+
+
+/* ******************* /reserves *************** */
+
+
+/**
+ * @brief Handle to a POST /reserves operation at a merchant's backend.
+ */
+struct TALER_MERCHANT_PostReservesHandle;
+
 
 /**
  * Callbacks of this type are used to work the result of submitting a
- * /track/transaction request to a merchant
+ * POST /reserves request to a merchant
  *
  * @param cls closure
- * @param http_status HTTP status code we got, 0 on exchange protocol violation
- * @param ec taler-specific error code
- * @param json original json reply from the backend
- * @param num_transfers number of wire transfers the exchange used for the 
transaction
- * @param transfers details about each transfer and which coins are aggregated 
in it
-*/
+ * @param hr HTTP response details
+ * @param reserve_pub public key of the created reserve, NULL on error
+ * @param payto_uri where to make the payment to for filling the reserve, NULL 
on error
+ */
 typedef void
-(*TALER_MERCHANT_TrackTransactionCallback) (
+(*TALER_MERCHANT_PostReservesCallback) (
   void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr);
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *payto_uri);
 
 
 /**
- * Request backend to return deposits associated with a given wtid.
+ * Request backend to create a reserve.
  *
  * @param ctx execution context
  * @param backend_url base URL of the backend
- * @param order_id which order should we trace
- * @param track_transaction_cb the callback to call when a reply for this 
request is available
- * @param track_transaction_cb_cls closure for @a track_transaction_cb
+ * @param initial_balance desired initial balance for the reserve
+ * @param exchange_url what is the URL of the exchange where the reserve 
should be set up
+ * @param wire_method desired wire method, for example "iban" or "x-taler-bank"
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
  * @return a handle for this request
  */
-struct TALER_MERCHANT_TrackTransactionHandle *
-TALER_MERCHANT_track_transaction (
+struct TALER_MERCHANT_PostReservesHandle *
+TALER_MERCHANT_reserves_post (
   struct GNUNET_CURL_Context *ctx,
   const char *backend_url,
-  const char *order_id,
-  TALER_MERCHANT_TrackTransactionCallback track_transaction_cb,
-  void *track_transaction_cb_cls);
+  const struct TALER_Amount *initial_balance,
+  const char *exchange_url,
+  const char *wire_method,
+  TALER_MERCHANT_PostReservesCallback cb,
+  void *cb_cls);
 
 
 /**
- * Cancel a /track/transaction request.  This function cannot be used
+ * Cancel a POST /reserves request.  This function cannot be used
  * on a request handle if a response is already served for it.
  *
- * @param tdo the tracking request to cancel
+ * @param pth the operation to cancel
+ */
+void
+TALER_MERCHANT_reserves_post_cancel (
+  struct TALER_MERCHANT_PostReservesHandle *prh);
+
+
+/**
+ * Handle for a GET /reserves operation.
+ */
+struct TALER_MERCHANT_ReservesGetHandle;
+
+
+/**
+ * Information about a reserve.
+ */
+struct TALER_MERCHANT_ReserveSummary
+{
+  /**
+   * Public key of the reserve
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Timestamp when it was established
+   */
+  struct GNUNET_TIME_Absolute creation_time;
+
+  /**
+   * Timestamp when it expires
+   */
+  struct GNUNET_TIME_Absolute expiration_time;
+
+  /**
+   * Initial amount as per reserve creation call
+   */
+  struct TALER_Amount merchant_initial_amount;
+
+  /**
+   * Initial amount as per exchange, 0 if exchange did
+   * not confirm reserve creation yet.
+   */
+  struct TALER_Amount exchange_initial_amount;
+
+  /**
+   * Amount picked up so far.
+   */
+  struct TALER_Amount pickup_amount;
+
+  /**
+   * Amount approved for tips that exceeds the pickup_amount.
+   */
+  struct TALER_Amount committed_amount;
+
+  /**
+   * Is this reserve active (false if it was deleted but not purged)
+   */
+  bool active;
+};
+
+
+/**
+ * Callback to process a GET /reserves request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param reserves_length length of the @a reserves array
+ * @param reserves array with details about the reserves, NULL on error
+ */
+typedef void
+(*TALER_MERCHANT_ReservesGetCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  unsigned int reserves_length,
+  const struct TALER_MERCHANT_ReserveSummary reserves[]);
+
+
+/**
+ * Issue a GET /reserves request to the backend.  Informs the backend
+ * that a customer wants to pick up a reserves.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param after filter for reserves created after this date, use 0 for no 
filtering
+ * @param active filter for reserves that are active
+ * @param failures filter for reserves where we disagree about the balance with
+ *        the exchange
+ * @param cb function to call with the result(s)
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_ReservesGetHandle *
+TALER_MERCHANT_reserves_get (struct GNUNET_CURL_Context *ctx,
+                             const char *backend_url,
+                             struct GNUNET_TIME_Absolute after,
+                             enum TALER_EXCHANGE_YesNoAll active,
+                             enum TALER_EXCHANGE_YesNoAll failures,
+                             TALER_MERCHANT_ReservesGetCallback cb,
+                             void *cb_cls);
+
+
+/**
+ * Cancel a GET /reserves request.
+ *
+ * @param rgh handle to the request to be canceled
  */
 void
-TALER_MERCHANT_track_transaction_cancel (
-  struct TALER_MERCHANT_TrackTransactionHandle *tdo);
+TALER_MERCHANT_reserves_get_cancel (
+  struct TALER_MERCHANT_ReservesGetHandle *rgh);
+
+
+/**
+ * Handle for a request to obtain details on a specific
+ * (tipping) reserve.
+ */
+struct TALER_MERCHANT_ReserveGetHandle;
+
+
+/**
+ * Details about a tip granted by the merchant.
+ */
+struct TALER_MERCHANT_TipDetails
+{
+  /**
+   * Identifier for the tip.
+   */
+  struct GNUNET_HashCode tip_id;
+
+  /**
+   * Total value of the tip (including fees).
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Human-readable reason for why the tip was granted.
+   */
+  const char *reason;
 
-/* ********************* /history *********************** */
+};
 
-struct TALER_MERCHANT_HistoryOperation;
 
 /**
- * Callback for a /history request. It's up to this function how
- * to render the array containing transactions details (FIXME link to
- * documentation)
+ * Callback to process a GET /reserve/$RESERVE_PUB request
  *
  * @param cls closure
  * @param hr HTTP response details
+ * @param rs reserve summary for the reserve, NULL on error
+ * @param active is this reserve active (false if it was deleted but not 
purged)
+ * @param tips_length length of the @a reserves array
+ * @param tips array with details about the tips granted, NULL on error
  */
 typedef void
-(*TALER_MERCHANT_HistoryOperationCallback) (
+(*TALER_MERCHANT_ReserveGetCallback) (
   void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr);
-
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct TALER_MERCHANT_ReserveSummary *rs,
+  bool active,
+  unsigned int tips_length,
+  const struct TALER_MERCHANT_TipDetails tips[]);
 
-/**
- * Issue a /history request to the backend.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @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 @a history_cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_HistoryOperation *
-TALER_MERCHANT_history (struct GNUNET_CURL_Context *ctx,
-                        const char *backend_url,
-                        unsigned long long start,
-                        long long delta,
-                        struct GNUNET_TIME_Absolute date,
-                        TALER_MERCHANT_HistoryOperationCallback history_cb,
-                        void *history_cb_cls);
 
 /**
- * Issue a /history request to the backend.
+ * Issue a GET /reserve/$RESERVE_ID request to the backend.  Queries the 
backend
+ * about the status of a reserve.
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
- * @param start return `delta` records starting from position `start`.
- * If given as zero, then no initial skip of `start` records is done.
- * @param delta return `delta` records starting from position `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 @a history_cb
+ * @param reserve_pub which reserve should be queried
+ * @param tips should we return details about the tips issued from the reserve
+ * @param cb function to call with the result(s)
+ * @param cb_cls closure for @a cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_HistoryOperation *
-TALER_MERCHANT_history_default_start (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  long long delta,
-  struct GNUNET_TIME_Absolute date,
-  TALER_MERCHANT_HistoryOperationCallback history_cb,
-  void *history_cb_cls);
+struct TALER_MERCHANT_ReserveGetHandle *
+TALER_MERCHANT_reserve_get (struct GNUNET_CURL_Context *ctx,
+                            const char *backend_url,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            bool fetch_tips,
+                            TALER_MERCHANT_ReserveGetCallback cb,
+                            void *cb_cls);
 
 
 /**
- * Cancel a pending /history request
+ * Cancel a GET /reserve/$RESERVE_ID request.
  *
- * @param ho handle from the operation to cancel
+ * @param rgh handle to the request to be canceled
  */
 void
-TALER_MERCHANT_history_cancel (struct TALER_MERCHANT_HistoryOperation *ho);
+TALER_MERCHANT_reserve_get_cancel (
+  struct TALER_MERCHANT_ReserveGetHandle *rgh);
 
 
-/* ********************** /tip-authorize ********************** */
-
 /**
  * Handle for a /tip-authorize operation.
  */
-struct TALER_MERCHANT_TipAuthorizeOperation;
+struct TALER_MERCHANT_TipAuthorizeHandle;
 
 
 /**
- * Callback for a /tip-authorize request.  Returns the result of
+ * Callback for a /reserves/$RESERVE_PUB/tip-authorize request.  Returns the 
result of
  * the operation.
  *
  * @param cls closure
  * @param hr HTTP response details
  * @param tip_id which tip ID should be used to pickup the tip
  * @param tip_uri URI for the tip
+ * @param tip_expiration when does the tip expire
  */
 typedef void
 (*TALER_MERCHANT_TipAuthorizeCallback) (
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
   struct GNUNET_HashCode *tip_id,
-  const char *tip_uri);
+  const char *tip_uri,
+  struct GNUNET_TIME_Absolute tip_expiration);
 
 
 /**
@@ -1118,7 +2681,33 @@ typedef void
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
- * @param pickup_url frontend URL for where the tip can be picked up
+ * @param reserve_pub public key of the reserve
+ * @param next_url where the browser should proceed after picking up the tip
+ * @param amount amount to be handed out as a tip
+ * @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_TipAuthorizeHandle *
+TALER_MERCHANT_tip_authorize2 (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *next_url,
+  const struct TALER_Amount *amount,
+  const char *justification,
+  TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
+  void *authorize_cb_cls);
+
+
+/**
+ * Issue a POST /tips request to the backend.  Informs the backend that a tip
+ * should be created. In contrast to #TALER_MERCHANT_tip_authorize2(), the
+ * backend gets to pick the reserve with this API.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
  * @param next_url where the browser should proceed after picking up the tip
  * @param amount amount to be handed out as a tip
  * @param justification which justification should be stored (human-readable 
reason for the tip)
@@ -1126,10 +2715,9 @@ typedef void
  * @param authorize_cb_cls closure to pass to @a authorize_cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_TipAuthorizeOperation *
+struct TALER_MERCHANT_TipAuthorizeHandle *
 TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context *ctx,
                               const char *backend_url,
-                              const char *pickup_url,
                               const char *next_url,
                               const struct TALER_Amount *amount,
                               const char *justification,
@@ -1144,358 +2732,443 @@ TALER_MERCHANT_tip_authorize (struct 
GNUNET_CURL_Context *ctx,
  */
 void
 TALER_MERCHANT_tip_authorize_cancel (
-  struct TALER_MERCHANT_TipAuthorizeOperation *ta);
-
-/* ********************** /tip-pickup ************************* */
+  struct TALER_MERCHANT_TipAuthorizeHandle *ta);
 
 
 /**
- * Handle for a /tip-pickup operation.
+ * Handle for a request to delete or purge a specific reserve.
  */
-struct TALER_MERCHANT_TipPickupOperation;
+struct TALER_MERCHANT_ReserveDeleteHandle;
 
 
 /**
- * Callback for a /tip-pickup request.  Returns the result of the operation.
+ * Callback to process a DELETE /reserve/$RESERVE_PUB request
  *
  * @param cls closure
  * @param hr HTTP response details
- * @param num_sigs length of the @a reserve_sigs array, 0 on error
- * @param sigs array of signatures over the coins, NULL on error
  */
 typedef void
-(*TALER_MERCHANT_TipPickupCallback) (
+(*TALER_MERCHANT_ReserveDeleteCallback) (
   void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr,
-  unsigned int num_sigs,
-  const struct TALER_DenominationSignature *sigs);
+  const struct TALER_MERCHANT_HttpResponse *hr);
 
 
 /**
- * Information per planchet.
+ * Issue a DELETE /reserve/$RESERVE_ID request to the backend.  Only
+ * deletes the private key of the reserve, preserves tipping data.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param reserve_pub which reserve should be queried
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_PlanchetData
-{
-  /**
-   * Planchet secrets.
-   */
-  struct TALER_PlanchetSecretsP ps;
-
-  /**
-   * Denomination key desired.
-   */
-  const struct TALER_EXCHANGE_DenomPublicKey *pk;
+struct TALER_MERCHANT_ReserveDeleteHandle *
+TALER_MERCHANT_reserve_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  TALER_MERCHANT_ReserveDeleteCallback cb,
+  void *cb_cls);
 
-};
 
 /**
- * Issue a /tip-pickup request to the backend.  Informs the backend
- * that a customer wants to pick up a tip.
+ * Issue a DELETE /reserve/$RESERVE_ID request to the backend.
+ * Purges the reserve, deleting all associated data. DANGEROUS.
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
- * @param tip_id unique identifier for the tip
- * @param num_planches number of planchets provided in @a pds
- * @param pds array of planchet secrets 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
+ * @param reserve_pub which reserve should be queried
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a 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_url,
-                           const struct GNUNET_HashCode *tip_id,
-                           unsigned int num_planchets,
-                           const struct TALER_MERCHANT_PlanchetData *pds,
-                           TALER_MERCHANT_TipPickupCallback pickup_cb,
-                           void *pickup_cb_cls);
+struct TALER_MERCHANT_ReserveDeleteHandle *
+TALER_MERCHANT_reserve_purge (struct GNUNET_CURL_Context *ctx,
+                              const char *backend_url,
+                              const struct TALER_ReservePublicKeyP 
*reserve_pub,
+                              TALER_MERCHANT_ReserveDeleteCallback cb,
+                              void *cb_cls);
 
 
 /**
- * Cancel a pending /tip-pickup request
+ * Cancel a DELETE (or purge) /reserve/$RESERVE_ID request.
  *
- * @param tp handle from the operation to cancel
+ * @param rdh handle to the request to be canceled
  */
 void
-TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupOperation 
*tp);
+TALER_MERCHANT_reserve_delete_cancel (
+  struct TALER_MERCHANT_ReserveDeleteHandle *rdh);
+
+
+/* ********************* /tips ************************** */
 
 
 /**
- * Handle for a low-level /tip-pickup operation (without unblinding).
+ * Handle for a GET /tips/$TIP_ID (public variant) operation.
  */
-struct TALER_MERCHANT_TipPickup2Operation;
+struct TALER_MERCHANT_TipWalletGetHandle;
+
 
 /**
- * A blind signature returned via tipping API.
+ * Callback to process a GET /tips/$TIP_ID request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param expiration when the tip will expire
+ * @param exchange_url exchange from which the coins should be withdrawn
+ * @param amount_remaining total amount still available for the tip
+ */
+typedef void
+(*TALER_MERCHANT_TipWalletGetCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  struct GNUNET_TIME_Absolute expiration,
+  const char *exchange_url,
+  const struct TALER_Amount *amount_remaining);
+
+
+/**
+ * Issue a GET /tips/$TIP_ID (public variant) request to the backend.  Returns
+ * information needed to pick up a tip.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param tip_id which tip should we query
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
  */
+struct TALER_MERCHANT_TipWalletGetHandle *
+TALER_MERCHANT_wallet_tip_get (struct GNUNET_CURL_Context *ctx,
+                               const char *backend_url,
+                               const struct GNUNET_HashCode *tip_id,
+                               TALER_MERCHANT_TipWalletGetCallback cb,
+                               void *cb_cls);
 
-struct TALER_MERCHANT_BlindSignature
+
+/**
+ * Cancel a GET /tips/$TIP_ID request.
+ *
+ * @param tqo handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_wallet_tip_get_cancel (struct
+                                      TALER_MERCHANT_TipWalletGetHandle *tgh);
+
+
+/**
+ * Handle for a GET /private/tips/$TIP_ID (private variant) operation.
+ */
+struct TALER_MERCHANT_TipMerchantGetHandle;
+
+
+/**
+ * Summary information for a tip pickup.
+ */
+struct TALER_MERCHANT_PickupDetail
 {
   /**
-   * We use RSA.
+   * Identifier of the pickup.
    */
-  const struct GNUNET_CRYPTO_RsaSignature *blind_sig;
-};
+  struct GNUNET_HashCode pickup_id;
+
+  /**
+   * Number of planchets involved.
+   */
+  uint64_t num_planchets;
 
+  /**
+   * Total amount requested for this pickup.
+   */
+  struct TALER_Amount requested_amount;
+};
 
 /**
- * Callback for a /tip-pickup request.  Returns the result of the operation.
- * Note that the client MUST still do the unblinding of the @a blind_sigs.
+ * Callback to process a GET /private/tips/$TIP_ID request
  *
  * @param cls closure
  * @param hr HTTP response details
- * @param num_blind_sigs length of the @a blind_sigs array, 0 on error
- * @param blind_sigs array of blind signatures over the planchets, NULL on 
error
+ * @param expiration when the tip will expire
+ * @param exchange_url exchange from which the coins should be withdrawn
+ * @param amount_remaining total amount still available for the tip
  */
 typedef void
-(*TALER_MERCHANT_TipPickup2Callback) (
+(*TALER_MERCHANT_TipMerchantGetCallback) (
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
-  unsigned int num_blind_sigs,
-  const struct TALER_MERCHANT_BlindSignature *blind_sigs);
+  const struct TALER_Amount *total_authorized,
+  const struct TALER_Amount *total_picked_up,
+  const char *reason,
+  struct GNUNET_TIME_Absolute expiration,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  unsigned int pickups_length,
+  const struct TALER_MERCHANT_PickupDetail pickups[]);
 
 
 /**
- * Issue a /tip-pickup request to the backend.  Informs the backend
- * that a customer wants to pick up a tip.
+ * Issue a GET /private/tips/$TIP_ID (private variant) request to the backend.
+ * Returns information needed to pick up a tip.
  *
  * @param ctx execution context
  * @param backend_url 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
+ * @param tip_id which tip should we query
+ * @param pickups whether to fetch associated pickups
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_TipPickup2Operation *
-TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context *ctx,
-                            const char *backend_url,
-                            const struct GNUNET_HashCode *tip_id,
-                            unsigned int num_planchets,
-                            struct TALER_PlanchetDetail *planchets,
-                            TALER_MERCHANT_TipPickup2Callback pickup_cb,
-                            void *pickup_cb_cls);
+struct TALER_MERCHANT_TipMerchantGetHandle *
+TALER_MERCHANT_merchant_tip_get (struct GNUNET_CURL_Context *ctx,
+                                 const char *backend_url,
+                                 const struct GNUNET_HashCode *tip_id,
+                                 bool pickups,
+                                 TALER_MERCHANT_TipMerchantGetCallback cb,
+                                 void *cb_cls);
 
 
 /**
- * Cancel a pending /tip-pickup request.
+ * Cancel a GET /private/tips/$TIP_ID request.
  *
- * @param tp handle from the operation to cancel
+ * @param tqo handle to the request to be canceled
  */
 void
-TALER_MERCHANT_tip_pickup2_cancel (
-  struct TALER_MERCHANT_TipPickup2Operation *tp);
+TALER_MERCHANT_merchant_tip_get_cancel (struct
+                                        TALER_MERCHANT_TipMerchantGetHandle 
*tgh);
 
 
-/* ********************** /check-payment ************************* */
+/**
+ * Handle for a GET /private/tips request.
+ */
+struct TALER_MERCHANT_TipsGetHandle;
 
 
 /**
- * Handle for a /check-payment operation.
+ * Database entry information of a tip.
  */
-struct TALER_MERCHANT_CheckPaymentOperation;
+struct TALER_MERCHANT_TipEntry
+{
+  /**
+   * Row number of the tip in the database.
+   */
+  uint64_t row_id;
+
+  /**
+   * Identifier for the tip.
+   */
+  struct GNUNET_HashCode tip_id;
+
+  /**
+   * Total value of the tip (including fees).
+   */
+  struct TALER_Amount tip_amount;
+
+};
 
 
 /**
- * Callback to process a GET /check-payment request
+ * Callback to process a GET /private/tips request.
  *
  * @param cls closure
  * @param hr HTTP response details
- * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not
- *        settled, $GNUNET_SYSERR on error
- *        (note that refunded payments are returned as paid!)
- * @param refunded #GNUNET_YES if there is at least on refund on this payment,
- *        #GNUNET_NO if refunded, #GNUNET_SYSERR or error
- * @param refunded_amount amount that was refunded, NULL if there
- *        was no refund
- * @param taler_pay_uri the URI that instructs the wallets to process
- *                      the payment
+ * @param tips_length length of the @a tips array
+ * @param tips the array of tips, NULL on error
  */
 typedef void
-(*TALER_MERCHANT_CheckPaymentCallback) (
+(*TALER_MERCHANT_TipsGetCallback) (
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
-  int paid,
-  int refunded,
-  struct TALER_Amount *refund_amount,
-  const char *taler_pay_uri);
+  unsigned int tips_length,
+  const struct TALER_MERCHANT_TipEntry tips[]);
 
 
 /**
- * Issue a /check-payment request to the backend.  Checks the status
- * of a payment.
+ * Issue a GET /private/tips request to the backend.
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
- * @param order_id order id to identify the payment
- * @param session_id sesion id for the payment (or NULL if the payment is not 
bound to a session)
- * @param timeout timeout to use in long polling (how long may the server wait 
to reply
- *        before generating an unpaid response). Note that this is just 
provided to
- *        the server, we as client will block until the response comes back or 
until
- *        #TALER_MERCHANT_check_payment_cancel() is called.
- * @param check_payment_cb callback which will work the response gotten from 
the backend
- * @param check_payment_cb_cls closure to pass to @a check_payment_cb
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_CheckPaymentOperation *
-TALER_MERCHANT_check_payment (struct GNUNET_CURL_Context *ctx,
-                              const char *backend_url,
-                              const char *order_id,
-                              const char *session_id,
-                              struct GNUNET_TIME_Relative timeout,
-                              TALER_MERCHANT_CheckPaymentCallback
-                              check_payment_cb,
-                              void *check_payment_cls);
+struct TALER_MERCHANT_TipsGetHandle *
+TALER_MERCHANT_tips_get (struct GNUNET_CURL_Context *ctx,
+                         const char *backend_url,
+                         TALER_MERCHANT_TipsGetCallback cb,
+                         void *cb_cls);
+
 
 /**
- * Cancel a GET /check-payment request.
+ * Issue a GET /private/tips request with filters to the backend.
  *
- * @param cpo handle to the request to be canceled
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param expired yes for expired tips, no for unexpired tips, all for all tips
+ * @param limit number of results to return, negative for descending row id, 
positive for ascending
+ * @param offset row id to start returning results from
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
  */
-void
-TALER_MERCHANT_check_payment_cancel (
-  struct TALER_MERCHANT_CheckPaymentOperation *cpo);
+struct TALER_MERCHANT_TipsGetHandle *
+TALER_MERCHANT_tips_get2 (struct GNUNET_CURL_Context *ctx,
+                          const char *backend_url,
+                          enum TALER_EXCHANGE_YesNoAll expired,
+                          int64_t limit,
+                          uint64_t offset,
+                          TALER_MERCHANT_TipsGetCallback cb,
+                          void *cb_cls);
+
 
+/**
+ * Cancel a GET /private/tips request.
+ *
+ * @param
+ */
+void
+TALER_MERCHANT_tips_get_cancel (struct TALER_MERCHANT_TipsGetHandle *);
 
-/* ********************** /tip-query ************************* */
 
 /**
- * Handle for a /tip-query operation.
+ * Handle for a POST /tips/$TIP_ID/pickup operation.
  */
-struct TALER_MERCHANT_TipQueryOperation;
+struct TALER_MERCHANT_TipPickupHandle;
 
 
 /**
- * Callback to process a GET /tip-query request
+ * Callback for a POST /tips/$TIP_ID/pickup request.  Returns the result of
+ * the operation.
  *
  * @param cls closure
  * @param hr HTTP response details
- * @param reserve_expiration when the tip reserve will expire
- * @param reserve_pub tip reserve public key
- * @param amount_authorized total amount authorized on tip reserve
- * @param amount_available total amount still available on tip reserve
- * @param amount_picked_up total amount picked up from tip reserve
+ * @param num_sigs length of the @a reserve_sigs array, 0 on error
+ * @param sigs array of signatures over the coins, NULL on error
  */
 typedef void
-(*TALER_MERCHANT_TipQueryCallback) (
+(*TALER_MERCHANT_TipPickupCallback) (
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
-  struct GNUNET_TIME_Absolute reserve_expiration,
-  struct TALER_ReservePublicKeyP *reserve_pub,
-  struct TALER_Amount *amount_authorized,
-  struct TALER_Amount *amount_available,
-  struct TALER_Amount *amount_picked_up);
+  unsigned int num_sigs,
+  const struct TALER_DenominationSignature sigs[]);
 
 
 /**
- * Cancel a GET /tip-query request.
- *
- * @param cph handle to the request to be canceled
+ * Information per planchet.
  */
-void
-TALER_MERCHANT_tip_query_cancel (struct TALER_MERCHANT_TipQueryOperation *tqo);
+struct TALER_MERCHANT_PlanchetData
+{
+  /**
+   * Planchet secrets.
+   */
+  struct TALER_PlanchetSecretsP ps;
+
+  /**
+   * Denomination key desired.
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *pk;
 
+};
 
 /**
- * Issue a /tip-query request to the backend.  Informs the backend
- * that a customer wants to pick up a tip.
+ * Issue a POST /tips/$TIP_ID/pickup request to the backend.  Informs the
+ * backend that a customer wants to pick up a tip.
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
+ * @param tip_id unique identifier for the tip
+ * @param num_planches number of planchets provided in @a pds
+ * @param planchets array of planchet secrets 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_TipQueryOperation *
-TALER_MERCHANT_tip_query (struct GNUNET_CURL_Context *ctx,
-                          const char *backend_url,
-                          TALER_MERCHANT_TipQueryCallback query_cb,
-                          void *query_cb_cls);
+struct TALER_MERCHANT_TipPickupHandle *
+TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
+                           const char *backend_url,
+                           const struct GNUNET_HashCode *tip_id,
+                           unsigned int num_planchets,
+                           const struct TALER_MERCHANT_PlanchetData 
planchets[],
+                           TALER_MERCHANT_TipPickupCallback pickup_cb,
+                           void *pickup_cb_cls);
 
 
 /**
- * Cancel a GET /tip-query request.
+ * Cancel a pending /tips/$TIP_ID/pickup request
  *
- * @param tqo handle to the request to be canceled
+ * @param tp handle from the operation to cancel
  */
 void
-TALER_MERCHANT_tip_query_cancel (struct TALER_MERCHANT_TipQueryOperation *tqh);
-
+TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupHandle *tph);
 
-/* ********************** /public/poll-payment ************************* */
 
+/**
+ * Handle for a low-level /tip-pickup operation (without unblinding).
+ */
+struct TALER_MERCHANT_TipPickup2Handle;
 
 /**
- * Handle for a /public/poll-payment operation.
+ * A blind signature returned via tipping API.
  */
-struct TALER_MERCHANT_PollPaymentOperation;
+
+struct TALER_MERCHANT_BlindSignature
+{
+  /**
+   * We use RSA.
+   */
+  const struct GNUNET_CRYPTO_RsaSignature *blind_sig;
+};
 
 
 /**
- * Callback to process a GET /poll-payment request
+ * Callback for a POST /tips/$TIP_ID/pickup request.  Returns the result of
+ * the operation.  Note that the client MUST still do the unblinding of the @a
+ * blind_sigs.
  *
  * @param cls closure
  * @param hr HTTP response details
- * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not
- *        settled, $GNUNET_SYSERR on error
- *        (note that refunded payments are returned as paid!)
- * @param refunded #GNUNET_YES if there is at least on refund on this payment,
- *        #GNUNET_NO if refunded, #GNUNET_SYSERR or error
- * @param refunded_amount amount that was refunded, NULL if there
- *        was no refund
- * @param taler_pay_uri the URI that instructs the wallets to process
- *                      the payment
+ * @param num_blind_sigs length of the @a blind_sigs array, 0 on error
+ * @param blind_sigs array of blind signatures over the planchets, NULL on 
error
  */
 typedef void
-(*TALER_MERCHANT_PollPaymentCallback) (
+(*TALER_MERCHANT_TipPickup2Callback) (
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
-  int paid,
-  int refunded,
-  struct TALER_Amount *refund_amount,
-  const char *taler_pay_uri);
+  unsigned int num_blind_sigs,
+  const struct TALER_MERCHANT_BlindSignature blind_sigs[]);
 
 
 /**
- * Issue a /poll-payment request to the backend.  Polls the status
- * of a payment.
+ * Issue a POST /tips/$TIP_ID/pickup request to the backend.  Informs the
+ * backend that a customer wants to pick up a tip.
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
- * @param order_id order id to identify the payment
- * @param h_contract hash of the contract for @a order_id
- * @param session_id sesion id for the payment (or NULL if the payment is not 
bound to a session)
- * @param timeout timeout to use in long polling (how long may the server wait 
to reply
- *        before generating an unpaid response). Note that this is just 
provided to
- *        the server, we as client will block until the response comes back or 
until
- *        #TALER_MERCHANT_poll_payment_cancel() is called.
- * @param min_refund long poll for the service to approve a refund exceeding 
this value;
- *        use NULL to not wait for any refund (only for payment). Only makes 
sense
- *        with a non-zero @a timeout.
- * @param poll_payment_cb callback which will work the response gotten from 
the backend
- * @param poll_payment_cb_cls closure to pass to @a poll_payment_cb
+ * @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_PollPaymentOperation *
-TALER_MERCHANT_poll_payment (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  const char *order_id,
-  const struct GNUNET_HashCode *h_contract,
-  const char *session_id,
-  struct GNUNET_TIME_Relative timeout,
-  const struct TALER_Amount *min_refund,
-  TALER_MERCHANT_PollPaymentCallback poll_payment_cb,
-  void *poll_payment_cls);
+struct TALER_MERCHANT_TipPickup2Handle *
+TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context *ctx,
+                            const char *backend_url,
+                            const struct GNUNET_HashCode *tip_id,
+                            unsigned int num_planchets,
+                            const struct TALER_PlanchetDetail planchets[],
+                            TALER_MERCHANT_TipPickup2Callback pickup_cb,
+                            void *pickup_cb_cls);
 
 
 /**
- * Cancel a GET /public/poll-payment request.
+ * Cancel a pending /tip-pickup request.
  *
- * @param cpo handle to the request to be canceled
+ * @param tp handle from the operation to cancel
  */
 void
-TALER_MERCHANT_poll_payment_cancel (
-  struct TALER_MERCHANT_PollPaymentOperation *cpo);
+TALER_MERCHANT_tip_pickup2_cancel (
+  struct TALER_MERCHANT_TipPickup2Handle *tp);
 
 
 #endif  /* _TALER_MERCHANT_SERVICE_H */
diff --git a/src/include/taler_merchant_testing_lib.h 
b/src/include/taler_merchant_testing_lib.h
index c14ee2e..7b97815 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -40,13 +40,13 @@
  * the port is available.
  *
  * @param config_filename configuration filename.
- *
  * @return the base url, or NULL upon errors.  Must be freed
  *         by the caller.
  */
 char *
 TALER_TESTING_prepare_merchant (const char *config_filename);
 
+
 /**
  * Start the merchant backend process.  Assume the port
  * is available and the database is clean.  Use the "prepare
@@ -55,7 +55,6 @@ TALER_TESTING_prepare_merchant (const char *config_filename);
  * @param config_filename configuration filename.
  * @param merchant_url merchant base URL, used to check
  *        if the merchant was started right.
- *
  * @return the process, or NULL if the process could not
  *         be started.
  */
@@ -81,6 +80,342 @@ TALER_TESTING_cmd_config (const char *label,
                           unsigned int http_code);
 
 
+/**
+ * Define a "GET /instances" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /instances request.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        product (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_instances (const char *label,
+                                          const char *merchant_url,
+                                          unsigned int http_status,
+                                          ...);
+
+
+/**
+ * Define a "POST /instances" CMD, simple version
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /instances request.
+ * @param instance_id the ID of the instance to create
+ * @param payto_uri payment URI to use
+ * @param currency currency to use for default fees
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_instances (const char *label,
+                                           const char *merchant_url,
+                                           const char *instance_id,
+                                           const char *payto_uri,
+                                           const char *currency,
+                                           unsigned int http_status);
+
+
+/**
+ * Define a "POST /instances" CMD.  Comprehensive version.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /instances request.
+ * @param instance_id the ID of the instance to query
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_instances2 (
+  const char *label,
+  const char *merchant_url,
+  const char *instance_id,
+  unsigned int accounts_length,
+  const char *payto_uris[],
+  const char *name,
+  json_t *address,
+  json_t *jurisdiction,
+  const char *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const char *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  unsigned int http_status);
+
+
+/**
+ * Define a "PATCH /instances/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        PATCH /instance request.
+ * @param instance_id the ID of the instance to query
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_instance (
+  const char *label,
+  const char *merchant_url,
+  const char *instance_id,
+  unsigned int payto_uris_length,
+  const char *payto_uris[],
+  const char *name,
+  json_t *address,
+  json_t *jurisdiction,
+  const char *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const char *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  unsigned int http_status);
+
+
+/**
+ * Define a "GET instance" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /instances/$ID request.
+ * @param instance_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @param instance_reference reference to a "POST /instances" or "PATCH 
/instances/$ID" CMD
+ *        that will provide what we expect the backend to return to us
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_instance (const char *label,
+                                         const char *merchant_url,
+                                         const char *instance_id,
+                                         unsigned int http_status,
+                                         const char *instance_reference);
+
+
+/**
+ * Define a "PURGE instance" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        PURGE /instances/$ID request.
+ * @param instance_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_purge_instance (const char *label,
+                                           const char *merchant_url,
+                                           const char *instance_id,
+                                           unsigned int http_status);
+
+
+/**
+ * Define a "DELETE instance" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /instances/$ID request.
+ * @param instance_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_instance (const char *label,
+                                            const char *merchant_url,
+                                            const char *instance_id,
+                                            unsigned int http_status);
+
+
+/* ******************* /products **************** */
+
+
+/**
+ * Define a "POST /products" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /products request.
+ * @param product_id the ID of the product to query
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books)
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_products2 (
+  const char *label,
+  const char *merchant_url,
+  const char *product_id,
+  const char *description,
+  json_t *description_i18n,
+  const char *unit,
+  const char *price,
+  json_t *image,
+  json_t *taxes,
+  int64_t total_stock,
+  json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  unsigned int http_status);
+
+
+/**
+ * Define a "POST /products" CMD, simple version
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /products request.
+ * @param product_id the ID of the product to create
+ * @param description name of the product
+ * @param price price of the product
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_products (const char *label,
+                                          const char *merchant_url,
+                                          const char *product_id,
+                                          const char *description,
+                                          const char *price,
+                                          unsigned int http_status);
+
+
+/**
+ * Define a "PATCH /products/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        PATCH /product request.
+ * @param product_id the ID of the product to query
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books)
+ * @param total_lost in @a units, must be larger than previous values, and may
+ *               not exceed total_stock minus total_sold; if it does, the 
transaction
+ *               will fail with a #MHD_HTTP_CONFLICT HTTP status code
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_product (
+  const char *label,
+  const char *merchant_url,
+  const char *product_id,
+  const char *description,
+  json_t *description_i18n,
+  const char *unit,
+  const char *price,
+  json_t *image,
+  json_t *taxes,
+  int64_t total_stock,
+  uint64_t total_lost,
+  json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  unsigned int http_status);
+
+
+/**
+ * Define a "GET /products" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /products request.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        product (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_products (const char *label,
+                                         const char *merchant_url,
+                                         unsigned int http_status,
+                                         ...);
+
+
+/**
+ * Define a "GET product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @param product_reference reference to a "POST /products" or "PATCH 
/products/$ID" CMD
+ *        that will provide what we expect the backend to return to us
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_product (const char *label,
+                                        const char *merchant_url,
+                                        const char *product_id,
+                                        unsigned int http_status,
+                                        const char *product_reference);
+
+
+/**
+ * Define a "DELETE product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_product (const char *label,
+                                           const char *merchant_url,
+                                           const char *product_id,
+                                           unsigned int http_status);
+
+
+/* ******************* /orders **************** */
+
 /**
  * Make the "proposal" command.
  *
@@ -93,29 +428,642 @@ TALER_TESTING_cmd_config (const char *label,
  * @return the command
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_proposal (const char *label,
-                            const char *merchant_url,
-                            unsigned int http_status,
-                            const char *order);
+TALER_TESTING_cmd_merchant_post_orders (const char *label,
+                                        const char *merchant_url,
+                                        unsigned int http_status,
+                                        const char *order);
+
+
+/**
+ * Define a "GET /orders" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /orders request.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        reserve (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_orders (const char *label,
+                                       const char *merchant_url,
+                                       unsigned int http_status,
+                                       ...);
+
+
+/**
+ * Start a long poll for GET /private/orders.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_orders_start (const char *label,
+                                     const char *merchant_url,
+                                     struct GNUNET_TIME_Relative timeout);
+
+
+/**
+ * Complete a long poll for GET /private/orders.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_orders_conclude (const char *label,
+                                        unsigned int http_status,
+                                        const char *poll_start_reference);
+
+
+/**
+ * Define a GET /orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param paid whether the order has been paid for or not.
+ * @param refunded whether the order has been refunded.
+ * @param http_status expected HTTP response code for the request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        refunds (commands) we expect to be aggregated in the transfer
+ *        (assuming @a http_code is #MHD_HTTP_OK). If @e refunded is false,
+ *        this parameter is ignored.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_order (const char *label,
+                                    const char *merchant_url,
+                                    const char *order_reference,
+                                    bool paid,
+                                    bool refunded,
+                                    unsigned int http_status,
+                                    ...);
+
+
+/**
+ * Define a GET /private/orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param paid whether the order has been paid for or not.
+ * @param refunded whether the order has been refunded.
+ * @param http_status expected HTTP response code for the request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        refunds (commands) we expect to be aggregated in the transfer
+ *        (assuming @a http_code is #MHD_HTTP_OK). If @e refunded is false,
+ *        this parameter is ignored.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_order (const char *label,
+                                      const char *merchant_url,
+                                      const char *order_reference,
+                                      bool paid,
+                                      bool refunded,
+                                      unsigned int http_status,
+                                      ...);
+
+
+/**
+ * Start a long poll for GET /private/orders/$ORDER_ID.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_order_start (const char *label,
+                                    const char *merchant_url,
+                                    const char *order_id,
+                                    struct GNUNET_TIME_Relative timeout);
+
+
+/**
+ * Complete a long poll for GET /private/orders/$ORDER_ID.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_order_conclude (const char *label,
+                                       unsigned int http_status,
+                                       const char *poll_start_reference);
+
+/**
+ * Make a "claim order" command.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant backend
+ *        serving the proposal lookup request.
+ * @param http_status expected HTTP response code.
+ * @param order_reference reference to a POST order CMD, can be NULL if @a 
order_id given
+ * @param order_id order id to lookup, can be NULL (then we use @a 
order_reference)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_claim_order (const char *label,
+                                        const char *merchant_url,
+                                        unsigned int http_status,
+                                        const char *order_reference,
+                                        const char *order_id);
+
+
+/**
+ * Make a "pay" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ *        is going to be checked.
+ * @param coin_reference reference to any command which is able
+ *        to provide coins to use for paying.
+ * @param amount_with_fee amount to pay, including the deposit
+ *        fee
+ * @param amount_without_fee amount to pay, no fees included.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_pay_order (const char *label,
+                                      const char *merchant_url,
+                                      unsigned int http_status,
+                                      const char *proposal_reference,
+                                      const char *coin_reference,
+                                      const char *amount_with_fee,
+                                      const char *amount_without_fee);
+
+/**
+ * Make an "abort" test command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to abort
+ * @param http_status expected HTTP response code
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_order_abort (const char *label,
+                                        const char *merchant_url,
+                                        const char *pay_reference,
+                                        unsigned int http_status);
+
+
+/**
+ * Define a "refund" order CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the backend serving the
+ *        "refund increase" request.
+ * @param reason refund justification, human-readable.
+ * @param order_id order id of the contract to refund.
+ * @param refund_amount amount to be refund-increased.
+ * @param http_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_order_refund (const char *label,
+                                         const char *merchant_url,
+                                         const char *reason,
+                                         const char *order_id,
+                                         const char *refund_amount,
+                                         unsigned int http_code);
+
+
+/**
+ * Define a "DELETE order" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /instances/$ID request.
+ * @param order_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_order (const char *label,
+                                         const char *merchant_url,
+                                         const char *order_id,
+                                         unsigned int http_status);
+
+
+/* ******************* /transfers *************** */
+
+
+/**
+ * Define a POST /transfers CMD.  Details like the WTID and
+ * other required parameters will be extracted from the bank
+ * history, using the latest transfer of the specified
+ * @a credit_amount to the @a merchant_url.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the backend serving the
+ *        "refund increase" request.
+ * @param auth credentials to access the exchange's bank account
+ * @param payto_rui URL of the exchange's bank account
+ * @param credit_amount amount credited
+ * @param http_code expected HTTP response code
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        deposit (commands) we expect to be aggregated in the transfer
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_transfer (
+  const char *label,
+  const struct TALER_BANK_AuthenticationData *auth,
+  const char *payto_uri,
+  const char *merchant_url,
+  const char *credit_amount,
+  unsigned int http_code,
+  ...);
+
+
+/**
+ * Define a GET /transfers CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the backend serving the
+ *        "refund increase" request.
+ * @param payto_uri payto URI to filter by, NULL for no filter
+ * @param http_code expected HTTP response code
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        transfer (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_transfers (const char *label,
+                                          const char *merchant_url,
+                                          const char *payto_uri,
+                                          unsigned int http_code,
+                                          ...);
+
+
+/* ******************* /reserves *************** */
+
+
+/**
+ * Define a "POST /reserves" CMD
+ *
+ * @param label command label.
+ * @param merchant_url url to the murchant.
+ * @param initial_balance initial amount in the reserve.
+ * @param exchange_url url to the exchange
+ * @param wire_method wire transfer method to use for this reserve
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_reserves (const char *label,
+                                          const char *merchant_url,
+                                          const char *initial_balance,
+                                          const char *exchange_url,
+                                          const char *wire_method,
+                                          unsigned int http_status);
+
+
+/**
+ * This commands does not query the backend at all,
+ * but just makes up a fake reserve.
+ *
+ * @param label command label.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_reserves_fake (const char *label);
+
+
+/**
+ * Define a "GET reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the request.
+ * @param http_status expected HTTP response code.
+ * @param reserve_reference reference to a "POST /reserves" that provides the
+ *        information we are expecting.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_reserve (const char *label,
+                                        const char *merchant_url,
+                                        unsigned int http_status,
+                                        const char *reserve_reference);
+
+
+/**
+ * Define a "GET reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the request.
+ * @param http_status expected HTTP response code.
+ * @param reserve_reference reference to a "POST /reserves" that provides the
+ *        information we are expecting.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        tip (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_reserve_with_tips (const char *label,
+                                                  const char *merchant_url,
+                                                  unsigned int http_status,
+                                                  const char 
*reserve_reference,
+                                                  ...);
+
+
+/**
+ * Define a "GET /reserves" CMD
+ *
+ * @param label command label.
+ * @param merchant_url url to the merchant.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        reserve (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_reserves (const char *label,
+                                         const char *merchant_url,
+                                         unsigned int http_status,
+                                         ...);
+
+
+/**
+ * Define a "DELETE reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /reserves/$RESERVE_PUB request.
+ * @param reserve_reference command label of a command providing a reserve
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_reserve (const char *label,
+                                           const char *merchant_url,
+                                           const char *reserve_reference,
+                                           unsigned int http_status);
+
+
+/**
+ * Define a "PURGE reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /reserves/$RESERVE_PUB request.
+ * @param reserve_reference command label of a command providing a reserve
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_purge_reserve (const char *label,
+                                          const char *merchant_url,
+                                          const char *reserve_reference,
+                                          unsigned int http_status);
+
+
+/**
+ * Define a get tips CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        server the /tip-query request.
+ * @param http_status expected HTTP response code for the
+ *        /tip-query request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        tip (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_tips (const char *label,
+                            const char *merchant_url,
+                            unsigned int http_status,
+                            ...);
+
+
+/**
+ * Define a GET /private/tips/$TIP_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_tip (const char *label,
+                                    const char *merchant_url,
+                                    const char *tip_reference,
+                                    unsigned int http_status);
+
+
+/**
+ * Define a GET /private/tips/$TIP_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param pickup_refs a NULL-terminated list of pickup commands
+ *        associated with the tip.
+ * @param http_status expected HTTP response code for the request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        pickup (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_tip_with_pickups (const char *label,
+                                                 const char *merchant_url,
+                                                 const char *tip_reference,
+                                                 unsigned int http_status,
+                                                 ...);
+
+/**
+ * Define a GET /tips/$TIP_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_tip (const char *label,
+                                  const char *merchant_url,
+                                  const char *tip_reference,
+                                  unsigned int http_status);
+
+
+/**
+ * Define a GET /tips/$TIP_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param amount_remaining the balance remaining after pickups.
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_tip2 (const char *label,
+                                   const char *merchant_url,
+                                   const char *tip_reference,
+                                   const char *amount_remaining,
+                                   unsigned int http_status);
+
+
+/**
+ * Create a /tip-authorize CMD.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ *        serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ *        the reserve from which the tip is going to be gotten.
+ * @param http_status the HTTP response code which is expected
+ *        for this operation.
+ * @param justification human-readable justification for this
+ *        tip authorization.
+ * @param amount the amount to authorize for tipping.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize (const char *label,
+                                 const char *merchant_url,
+                                 const char *exchange_url,
+                                 unsigned int http_status,
+                                 const char *justification,
+                                 const char *amount);
+
+
+/**
+ * Create a /tip-authorize CMD.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ *        serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ *        the reserve from which the tip is going to be gotten.
+ * @param reserve_reference reference to a command that created
+ *        a reserve.
+ * @param http_status the HTTP response code which is expected
+ *        for this operation.
+ * @param justification human-readable justification for this
+ *        tip authorization.
+ * @param amount the amount to authorize for tipping.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_from_reserve (const char *label,
+                                              const char *merchant_url,
+                                              const char *exchange_url,
+                                              const char *reserve_refernce,
+                                              unsigned int http_status,
+                                              const char *justification,
+                                              const char *amount);
+
+
+/**
+ * Create a /tip-authorize CMD, specifying the Taler error code
+ * that is expected to be returned by the backend.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ *        serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ *        the reserve from which the tip is going to be gotten.
+ * @param http_status the HTTP response code which is expected
+ *        for this operation.
+ * @param justification human-readable justification for this
+ *        tip authorization.
+ * @param amount the amount to authorize for tipping.
+ * @param ec expected Taler-defined error code.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_with_ec (const char *label,
+                                         const char *merchant_url,
+                                         const char *exchange_url,
+                                         unsigned int http_status,
+                                         const char *justification,
+                                         const char *amount,
+                                         enum TALER_ErrorCode ec);
+
+
+/**
+ * Create a /tip-authorize CMD, specifying the Taler error code
+ * that is expected to be returned by the backend.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ *        serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ *        the reserve from which the tip is going to be gotten.
+ * @param reserve_reference reference to a command that created
+ *        a reserve.
+ * @param http_status the HTTP response code which is expected
+ *        for this operation.
+ * @param justification human-readable justification for this
+ *        tip authorization.
+ * @param amount the amount to authorize for tipping.
+ * @param ec expected Taler-defined error code.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec (const char *label,
+                                                      const char *merchant_url,
+                                                      const char *exchange_url,
+                                                      const char *
+                                                      reserve_reference,
+                                                      unsigned int http_status,
+                                                      const char 
*justification,
+                                                      const char *amount,
+                                                      enum TALER_ErrorCode ec);
+
+
+/**
+ * This commands does not query the backend at all,
+ * but just makes up a fake authorization id that will
+ * be subsequently used by the "pick up" CMD in order
+ * to test against such a case.
+ *
+ * @param label command label.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_fake (const char *label);
+
+
+/**
+ * Define a /tip-pickup CMD, equipped with the expected error
+ * code.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the backend which will serve
+ *        the /tip-pickup request.
+ * @param http_status expected HTTP response code.
+ * @param authorize_reference reference to a /tip-autorize CMD
+ *        that offers a tip id to pick up.
+ * @param amounts array of string-defined amounts that specifies
+ *        which denominations will be accepted for tipping.
+ * @param ec expected Taler error code.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_pickup_with_ec (const char *label,
+                                      const char *merchant_url,
+                                      unsigned int http_status,
+                                      const char *authorize_reference,
+                                      const char **amounts,
+                                      enum TALER_ErrorCode ec);
 
 /**
- * Make a "proposal lookup" command.
+ * Define a /tip-pickup CMD.
  *
- * @param label command label.
- * @param merchant_url base URL of the merchant backend
- *        serving the proposal lookup request.
+ * @param label the command label
+ * @param merchant_url base URL of the backend which will serve
+ *        the /tip-pickup request.
  * @param http_status expected HTTP response code.
- * @param proposal_reference reference to a "proposal" CMD.
- * @param order_id order id to lookup, can be NULL.
- *
- * @return the command.
+ * @param authorize_reference reference to a /tip-autorize CMD
+ *        that offers a tip id to pick up.
+ * @param amounts array of string-defined amounts that specifies
+ *        which denominations will be accepted for tipping.
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_proposal_lookup (const char *label,
-                                   const char *merchant_url,
-                                   unsigned int http_status,
-                                   const char *proposal_reference,
-                                   const char *order_id);
+TALER_TESTING_cmd_tip_pickup (const char *label,
+                              const char *merchant_url,
+                              unsigned int http_status,
+                              const char *authorize_reference,
+                              const char **amounts);
+
+
+/* ******************** OLD ******************* */
+
 
 /**
  * Make a "check payment" test command.
@@ -209,92 +1157,6 @@ TALER_TESTING_cmd_poll_payment_conclude (const char 
*label,
                                          unsigned int expect_paid);
 
 
-/**
- * Make a "pay" test command.
- *
- * @param label command label.
- * @param merchant_url merchant base url
- * @param http_status expected HTTP response code.
- * @param proposal_reference the proposal whose payment status
- *        is going to be checked.
- * @param coin_reference reference to any command which is able
- *        to provide coins to use for paying.
- * @param amount_with_fee amount to pay, including the deposit
- *        fee
- * @param amount_without_fee amount to pay, no fees included.
- * @param refund_fee fee for refunding this payment.
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay (const char *label,
-                       const char *merchant_url,
-                       unsigned int http_status,
-                       const char *proposal_reference,
-                       const char *coin_reference,
-                       const char *amount_with_fee,
-                       const char *amount_without_fee,
-                       const char *refund_fee);
-
-/**
- * Make a "pay again" test command.  Its purpose is to
- * take all the data from a aborted "pay" CMD, and use
- * good coins - found in @a coin_reference - to correctly
- * pay for it.
- *
- * @param label command label
- * @param merchant_url merchant base URL
- * @param pay_reference reference to the payment to replay
- * @param coin_reference reference to the coins to use
- * @param http_status expected HTTP response code
- *
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay_again (const char *label,
-                             const char *merchant_url,
-                             const char *pay_reference,
-                             const char *coin_reference,
-                             const char *refund_fee,
-                             unsigned int http_status);
-
-/**
- * Make a "pay abort" test command.
- *
- * @param label command label
- * @param merchant_url merchant base URL
- * @param pay_reference reference to the payment to abort
- * @param http_status expected HTTP response code
- *
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay_abort (const char *label,
-                             const char *merchant_url,
-                             const char *pay_reference,
-                             unsigned int http_status);
-
-/**
- * Make a "pay abort refund" CMD.  This command uses the
- * refund permission from a "pay abort" CMD, and redeems it
- * at the exchange.
- *
- * @param label command label.
- * @param abort_reference reference to the "pay abort" CMD that
- *        will offer the refund permission.
- * @param num_coins how many coins are expected to be refunded.
- * @param refund_amount the amount we are going to redeem as
- *        refund.
- * @param refund_fee the refund fee (FIXME: who pays it?)
- * @param http_status expected HTTP response code.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay_abort_refund (const char *label,
-                                    const char *abort_reference,
-                                    unsigned int num_coins,
-                                    const char *refund_amount,
-                                    const char *refund_fee,
-                                    unsigned int http_status);
-
 /**
  * Define a "refund lookup" CMD.
  *
@@ -351,29 +1213,6 @@ TALER_TESTING_cmd_refund_lookup_with_amount (const char 
*label,
                                              const char *refund_amount);
 
 
-/**
- * Define a "refund increase" CMD.
- *
- * @param label command label.
- * @param merchant_url base URL of the backend serving the
- *        "refund increase" request.
- * @param reason refund justification, human-readable.
- * @param order_id order id of the contract to refund.
- * @param refund_amount amount to be refund-increased.
- * @param refund_fee refund fee.
- * @param http_code expected HTTP response code.
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_refund_increase (const char *label,
-                                   const char *merchant_url,
-                                   const char *reason,
-                                   const char *order_id,
-                                   const char *refund_amount,
-                                   const char *refund_fee,
-                                   unsigned int http_code);
-
 /**
  * Make a "history" command.
  *
@@ -416,21 +1255,6 @@ TALER_TESTING_cmd_history (const char *label,
                            unsigned long long start,
                            long long nrows);
 
-/**
- * Define a "track transaction" CMD.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        /track/transaction request.
- * @param http_status expected HTTP response code.
- * @param pay_reference used to retrieve the order id to track.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_track_transaction (const char *label,
-                                              const char *merchant_url,
-                                              unsigned int http_status,
-                                              const char *pay_reference);
-
 /**
  * Define a "track transfer" CMD.
  *
@@ -480,6 +1304,38 @@ TALER_TESTING_get_trait_merchant_sig (
   unsigned int index,
   struct TALER_MerchantSignatureP **merchant_sig);
 
+
+/**
+ * Offer an order claim nonce.
+ *
+ * @param index which nonce to offer if there are
+ *        multiple on offer.
+ * @param nonce set to the offered nonce.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_claim_nonce (unsigned int index,
+                                      const struct
+                                      GNUNET_CRYPTO_EddsaPublicKey *nonce);
+
+
+/**
+ * Obtain an order claim nonce from a @a cmd.
+ *
+ * @param cmd command to extract the trait from.
+ * @param index which nonce to pick if @a
+ *        cmd has multiple on offer
+ * @param nonce[out] set to the wanted data.
+ *
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_claim_nonce (const struct TALER_TESTING_Command *cmd,
+                                     unsigned int index,
+                                     const struct
+                                     GNUNET_CRYPTO_EddsaPublicKey **nonce);
+
+
 /**
  * Obtain a reference to a proposal command.  Any command that
  * works with proposals, might need to offer their reference to
@@ -639,9 +1495,9 @@ TALER_TESTING_get_trait_h_contract_terms (
  * @return the trait
  */
 struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_refund_entry (
+TALER_TESTING_make_trait_refund_entry ( // FIXME: rename: entry->detail
   unsigned int index,
-  const struct TALER_MERCHANT_RefundEntry *refund_entry);
+  const struct TALER_MERCHANT_RefundDetail *refund_entry);
 
 
 /**
@@ -653,165 +1509,10 @@ TALER_TESTING_make_trait_refund_entry (
  * @return #GNUNET_OK on success
  */
 int
-TALER_TESTING_get_trait_refund_entry (
+TALER_TESTING_get_trait_refund_entry ( // FIXME: rename: entry->detail
   const struct TALER_TESTING_Command *cmd,
   unsigned int index,
-  const struct TALER_MERCHANT_RefundEntry **refund_entry);
-
-/**
- * Create a /tip-authorize CMD, specifying the Taler error code
- * that is expected to be returned by the backend.
- *
- * @param label this command label
- * @param merchant_url the base URL of the merchant that will
- *        serve the /tip-authorize request.
- * @param exchange_url the base URL of the exchange that owns
- *        the reserve from which the tip is going to be gotten.
- * @param http_status the HTTP response code which is expected
- *        for this operation.
- * @param justification human-readable justification for this
- *        tip authorization.
- * @param amount the amount to authorize for tipping.
- * @param ec expected Taler-defined error code.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_with_ec (const char *label,
-                                         const char *merchant_url,
-                                         const char *exchange_url,
-                                         unsigned int http_status,
-                                         const char *justification,
-                                         const char *amount,
-                                         enum TALER_ErrorCode ec);
-
-
-/**
- * This commands does not query the backend at all,
- * but just makes up a fake authorization id that will
- * be subsequently used by the "pick up" CMD in order
- * to test against such a case.
- *
- * @param label command label.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize_fake (const char *label);
-
-
-/**
- * Create a /tip-authorize CMD.
- *
- * @param label this command label
- * @param merchant_url the base URL of the merchant that will
- *        serve the /tip-authorize request.
- * @param exchange_url the base URL of the exchange that owns
- *        the reserve from which the tip is going to be gotten.
- * @param http_status the HTTP response code which is expected
- *        for this operation.
- * @param justification human-readable justification for this
- *        tip authorization.
- * @param amount the amount to authorize for tipping.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_authorize (const char *label,
-                                 const char *merchant_url,
-                                 const char *exchange_url,
-                                 unsigned int http_status,
-                                 const char *justification,
-                                 const char *amount);
-
-/**
- * Define a /tip-query CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- *        server the /tip-query request.
- * @param http_status expected HTTP response code for the
- *        /tip-query request.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_query (const char *label,
-                             const char *merchant_url,
-                             unsigned int http_status);
-
-/**
- * Define a /tip-query CMD equipped with a expected amount.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- *        server the /tip-query request.
- * @param http_status expected HTTP response code for the
- *        /tip-query request.
- * @param expected_amount_picked_up expected amount already
- *        picked up.
- * @param expected_amount_authorized expected amount that was
- *        authorized in the first place.
- * @param expected_amount_available FIXME what is this?
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_query_with_amounts (const char *label,
-                                          const char *merchant_url,
-                                          unsigned int http_status,
-                                          const char 
*expected_amount_picked_up,
-                                          const char 
*expected_amount_authorized,
-                                          const char 
*expected_amount_available);
-
-/**
- * Define a /tip-pickup CMD, equipped with the expected error
- * code.
- *
- * @param label the command label
- * @param merchant_url base URL of the backend which will serve
- *        the /tip-pickup request.
- * @param http_status expected HTTP response code.
- * @param authorize_reference reference to a /tip-autorize CMD
- *        that offers a tip id to pick up.
- * @param amounts array of string-defined amounts that specifies
- *        which denominations will be accepted for tipping.
- * @param ec expected Taler error code.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_pickup_with_ec (const char *label,
-                                      const char *merchant_url,
-                                      unsigned int http_status,
-                                      const char *authorize_reference,
-                                      const char **amounts,
-                                      enum TALER_ErrorCode ec);
-
-/**
- * Define a /tip-pickup CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the backend which will serve
- *        the /tip-pickup request.
- * @param http_status expected HTTP response code.
- * @param authorize_reference reference to a /tip-autorize CMD
- *        that offers a tip id to pick up.
- * @param amounts array of string-defined amounts that specifies
- *        which denominations will be accepted for tipping.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_pickup (const char *label,
-                              const char *merchant_url,
-                              unsigned int http_status,
-                              const char *authorize_reference,
-                              const char **amounts);
+  const struct TALER_MERCHANT_RefundDetail **refund_entry);
 
-/**
- * Make the instruction pointer point to @a new_ip
- * only if @a counter is greater than zero.
- *
- * @param label command label
- * @param new_ip new instruction pointer's value.  Note that,
- *    when the next instruction will be called, the interpreter
- *    will increment the ip _anyway_ so this value must be
- *    set to the index of the instruction we want to execute next
- *    MINUS one.
- * @param counter counts how many times the rewinding has
- * to happen.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_rewind_ip (const char *label,
-                             int new_ip,
-                             unsigned int *counter);
 
 #endif
diff --git a/src/include/taler_merchantdb_lib.h 
b/src/include/taler_merchantdb_lib.h
index eba702b..5ebbf73 100644
--- a/src/include/taler_merchantdb_lib.h
+++ b/src/include/taler_merchantdb_lib.h
@@ -37,7 +37,7 @@ struct TALER_MERCHANTDB_Plugin;
  * @return connection to the database; NULL upon error
  */
 struct TALER_MERCHANTDB_Plugin *
-TALER_MERCHANTDB_plugin_load (struct GNUNET_CONFIGURATION_Handle *cfg);
+TALER_MERCHANTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg);
 
 
 /**
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 57ec69f..71b28fb 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -24,6 +24,7 @@
 
 #include <gnunet/gnunet_util_lib.h>
 #include <gnunet/gnunet_db_lib.h>
+#include <taler/taler_exchange_service.h>
 #include <jansson.h>
 
 /**
@@ -33,459 +34,1629 @@ struct TALER_MERCHANTDB_Plugin;
 
 
 /**
- * Typically called by `find_contract_terms_by_date`.
- *
- * @param cls closure
- * @param order_id order id
- * @param row_id serial numer of the transaction in the table,
- * @param contract_terms proposal data related to order id
+ * Details about a wire account of the merchant.
  */
-typedef void
-(*TALER_MERCHANTDB_ProposalDataCallback)(void *cls,
-                                         const char *order_id,
-                                         uint64_t row_id,
-                                         const json_t *contract_terms);
+struct TALER_MERCHANTDB_AccountDetails
+{
+  /**
+   * Hash of the wire details (@e payto_uri and @e salt).
+   */
+  struct GNUNET_HashCode h_wire;
+
+  /**
+   * Salt value used for hashing @e payto_uri.
+   */
+  struct GNUNET_HashCode salt;
+
+  /**
+   * Actual account address as a payto://-URI.
+   */
+  const char *payto_uri;
+
+  /**
+   * Is the account set for active use in new contracts?
+   */
+  bool active;
+
+};
+
+
+/**
+ * General settings for an instance.
+ */
+struct TALER_MERCHANTDB_InstanceSettings
+{
+  /**
+   * prefix for the instance under "/instances/"
+   */
+  char *id;
+
+  /**
+   * legal name of the instance
+   */
+  char *name;
+
+  /**
+   * Address of the business
+   */
+  json_t *address;
+
+  /**
+   * jurisdiction of the business
+   */
+  json_t *jurisdiction;
+
+  /**
+   * 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 maximum wire fee to assume, unless stated differently in the
+   * proposal already.
+   */
+  struct TALER_Amount default_max_wire_fee;
+
+  /**
+   * Default factor for wire fee amortization.
+   */
+  uint32_t default_wire_fee_amortization;
+
+  /**
+   * If the frontend does NOT specify an execution date, how long should
+   * we tell the exchange to wait to aggregate transactions before
+   * executing the wire transfer?  This delay is added to the current
+   * time when we generate the advisory execution time for the exchange.
+   */
+  struct GNUNET_TIME_Relative default_wire_transfer_delay;
+
+  /**
+   * If the frontend does NOT specify a payment deadline, how long should
+   * offers we make be valid by default?
+   */
+  struct GNUNET_TIME_Relative default_pay_delay;
+
+};
+
 
 /**
- * Function called with information about a transaction.
+ * Typically called by `lookup_instances`.
  *
  * @param cls closure
- * @param merchant_pub merchant's public key
- * @param h_contract_terms proposal data's hashcode
- * @param h_wire hash of our wire details
- * @param timestamp time of the confirmation
- * @param refund refund deadline
- * @param total_amount total amount we receive for the contract after fees
+ * @param merchant_pub public key of the instance
+ * @param merchant_priv private key of the instance, NULL if not available
+ * @param is general instance settings
+ * @param accounts_length length of the @a accounts array
+ * @param accounts list of accounts of the merchant
  */
 typedef void
-(*TALER_MERCHANTDB_TransactionCallback)(
+(*TALER_MERCHANTDB_InstanceCallback)(
   void *cls,
   const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const struct GNUNET_HashCode *h_contract_terms,
-  const struct GNUNET_HashCode *h_wire,
-  struct GNUNET_TIME_Absolute timestamp,
-  struct GNUNET_TIME_Absolute refund,
-  const struct TALER_Amount *total_amount);
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  const struct TALER_MERCHANTDB_InstanceSettings *is,
+  unsigned int accounts_length,
+  const struct TALER_MERCHANTDB_AccountDetails accounts[]);
+
+
+/**
+ * Typically called by `lookup_products`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param product_id ID of the product
+ */
+typedef void
+(*TALER_MERCHANTDB_ProductsCallback)(void *cls,
+                                     const char *product_id);
+
+
+/**
+ * Details about a product.
+ */
+struct TALER_MERCHANTDB_ProductDetails
+{
+  /**
+   * Description of the product.
+   */
+  char *description;
+
+  /**
+   * Internationalized description.
+   */
+  json_t *description_i18n;
+
+  /**
+   * Unit in which the product is sold.
+   */
+  char *unit;
+
+  /**
+   * Price per unit of the product.  Zero to imply that the
+   * product is not sold separately or that the price is not fixed.
+   */
+  struct TALER_Amount price;
+
+  /**
+   * List of taxes the merchant pays for this product. Never NULL,
+   * but can be an empty array.
+   */
+  json_t *taxes;
+
+  /**
+   * Number of units of the product in stock in sum in total, including all
+   * existing sales and lost product, in product-specific units. UINT64_MAX
+   * indicates "infinite".
+   */
+  uint64_t total_stock;
+
+  /**
+   * Number of units of the product in sold, in product-specific units.
+   */
+  uint64_t total_sold;
+
+  /**
+   * Number of units of stock lost.
+   */
+  uint64_t total_lost;
+
+  /**
+   * Base64-encoded product image, or an empty string.
+   */
+  json_t *image;
+
+  /**
+   * Identifies where the product is in stock, possibly an empty map.
+   */
+  json_t *address;
+
+  /**
+   * Identifies when the product will be restocked. 0 for unknown,
+   * #GNUNET_TIME_UNIT_FOREVER_ABS for never.
+   */
+  struct GNUNET_TIME_Absolute next_restock;
+};
+
+
+/**
+ * Filter preferences.
+ */
+struct TALER_MERCHANTDB_OrderFilter
+{
+  /**
+   * Filter by payment status.
+   */
+  enum TALER_EXCHANGE_YesNoAll paid;
+
+  /**
+   * Filter by refund status.
+   */
+  enum TALER_EXCHANGE_YesNoAll refunded;
+
+  /**
+   * Filter by wire transfer status.
+   */
+  enum TALER_EXCHANGE_YesNoAll wired;
+
+  /**
+   * Filter orders by date, exact meaning depends on @e delta.
+   */
+  struct GNUNET_TIME_Absolute date;
+
+  /**
+   * Filter orders by order serial number, exact meaning depends on @e delta.
+   */
+  uint64_t start_row;
+
+  /**
+   * takes value of the form N (-N), so that at most N values strictly older
+   * (younger) than start and date are returned.
+   */
+  int64_t delta;
+
+  /**
+   * Timeout for long-polling.
+   */
+  struct GNUNET_TIME_Relative timeout;
+
+};
+
+
+/**
+ * Typically called by `lookup_orders`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param order_id ID of the order
+ * @param order_serial row of the order in the database
+ * @param timestamp creation time of the order in the database
+ */
+typedef void
+(*TALER_MERCHANTDB_OrdersCallback)(void *cls,
+                                   const char *order_id,
+                                   uint64_t order_serial,
+                                   struct GNUNET_TIME_Absolute timestamp);
 
 
 /**
  * Function called with information about a coin that was deposited.
  *
  * @param cls closure
- * @param h_contract_terms proposal data's hashcode
+ * @param exchange_url exchange where @a coin_pub was deposited
  * @param coin_pub public key of the coin
- * @param exchange_url URL of the exchange that issued the coin
  * @param amount_with_fee amount the exchange will deposit for this coin
  * @param deposit_fee fee the exchange will charge for this coin
  * @param refund_fee fee the exchange will charge for refunding this coin
  * @param wire_fee wire fee the exchange charges
- * @param exchange_proof proof from exchange that coin was accepted,
- *        matches the `interface DepositSuccess` of the documentation.
  */
 typedef void
-(*TALER_MERCHANTDB_CoinDepositCallback)(
+(*TALER_MERCHANTDB_DepositsCallback)(
   void *cls,
-  const struct GNUNET_HashCode *h_contract_terms,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const char *exchange_url,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const struct TALER_Amount *amount_with_fee,
   const struct TALER_Amount *deposit_fee,
   const struct TALER_Amount *refund_fee,
-  const struct TALER_Amount *wire_fee,
-  const json_t *exchange_proof);
+  const struct TALER_Amount *wire_fee);
 
 
 /**
- * Information about the wire transfer corresponding to
- * a deposit operation.  Note that it is in theory possible
- * that we have a @a h_contract_terms and @a coin_pub in the
- * result that do not match a deposit that we know about,
- * for example because someone else deposited funds into
- * our account.
+ * Function called with information about a refund.
  *
  * @param cls closure
- * @param h_contract_terms hashcode of the proposal data
- * @param coin_pub public key of the coin
- * @param wtid identifier of the wire transfer in which the exchange
- *             send us the money for the coin deposit
- * @param execution_time when was the wire transfer executed?
- * @param exchange_proof proof from exchange about what the deposit was for
- *             NULL if we have not asked for this signature
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  */
 typedef void
-(*TALER_MERCHANTDB_TransferCallback)(
+(*TALER_MERCHANTDB_RefundCallback)(
   void *cls,
-  const struct GNUNET_HashCode *h_contract_terms,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  const struct TALER_WireTransferIdentifierRawP *wtid,
-  struct GNUNET_TIME_Absolute execution_time,
-  const json_t *exchange_proof);
+  const struct TALER_Amount *refund_amount);
 
 
 /**
- * Function called with information about a wire transfer identifier.
+ * Typically called by `lookup_transfer_details_by_order`.
  *
  * @param cls closure
- * @param proof proof from exchange about what the wire transfer was for
+ * @param wtid wire transfer subject of the wire transfer for the coin
+ * @param exchange_url base URL of the exchange that made the payment
+ * @param execution_time when was the payment made
+ * @param deposit_value contribution of the coin to the total wire transfer 
value
+ * @param deposit_fee deposit fee charged by the exchange for the coin
+ * @param transfer_confirmed did the merchant confirm that a wire transfer with
+ *        @a wtid over the total amount happened?
  */
 typedef void
-(*TALER_MERCHANTDB_ProofCallback)(void *cls,
-                                  const json_t *proof);
+(*TALER_MERCHANTDB_OrderTransferDetailsCallback)(
+  void *cls,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const char *exchange_url,
+  struct GNUNET_TIME_Absolute execution_time,
+  const struct TALER_Amount *deposit_value,
+  const struct TALER_Amount *deposit_fee,
+  bool transfer_confirmed);
 
 
 /**
- * Function called with information about a refund.
+ * Function called with detailed information about a refund.
  *
  * @param cls closure
+ * @param refund_serial unique serial number of the refund
+ * @param timestamp time of the refund (for grouping of refunds in the wallet 
UI)
  * @param coin_pub public coin from which the refund comes from
  * @param exchange_url URL of the exchange that issued @a coin_pub
  * @param rtransaction_id identificator of the refund
  * @param reason human-readable explanation of the refund
  * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param refund_fee cost of this refund operation
  */
 typedef void
-(*TALER_MERCHANTDB_RefundCallback)(
+(*TALER_MERCHANTDB_RefundDetailCallback)(
   void *cls,
+  uint64_t refund_serial,
+  struct GNUNET_TIME_Absolute timestamp,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const char *exchange_url,
   uint64_t rtransaction_id,
   const char *reason,
-  const struct TALER_Amount *refund_amount,
-  const struct TALER_Amount *refund_fee);
+  const struct TALER_Amount *refund_amount);
 
 
 /**
- * Handle to interact with the database.
- *
- * Functions ending with "_TR" run their OWN transaction scope
- * and MUST NOT be called from within a transaction setup by the
- * caller.  Functions ending with "_NT" require the caller to
- * setup a transaction scope.  Functions without a suffix are
- * simple, single SQL queries that MAY be used either way.
+ * Results from trying to increase a refund.
  */
-struct TALER_MERCHANTDB_Plugin
+enum TALER_MERCHANTDB_RefundStatus
 {
 
   /**
-   * Closure for all callbacks.
+   * Refund amount exceeds original payment.
    */
-  void *cls;
+  TALER_MERCHANTDB_RS_TOO_HIGH = -3,
 
   /**
-   * Name of the library which generated this plugin.  Set by the
-   * plugin loader.
+   * Hard database failure.
    */
-  char *library_name;
+  TALER_MERCHANTDB_RS_HARD_ERROR = -2,
 
   /**
-   * Drop merchant tables. Used for testcases.
-   *
-   * @param cls closure
-   * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+   * Soft database failure.
    */
-  int
-  (*drop_tables) (void *cls);
+  TALER_MERCHANTDB_RS_SOFT_ERROR = -1,
 
   /**
-   * Insert order into db.
-   *
-   * @param cls closure
-   * @param order_id alphanumeric string that uniquely identifies the proposal
-   * @param merchant_pub merchant's public key
-   * @param timestamp timestamp of this proposal data
-   * @param contract_terms proposal data to store
-   * @return transaction status
+   * Order not found.
    */
-  enum GNUNET_DB_QueryStatus
-  (*insert_order)(void *cls,
-                  const char *order_id,
-                  const struct TALER_MerchantPublicKeyP *merchant_pub,
-                  struct GNUNET_TIME_Absolute timestamp,
-                  const json_t *contract_terms);
-
+  TALER_MERCHANTDB_RS_NO_SUCH_ORDER = 0,
 
   /**
-   * Insert proposal data into db; the routine will internally hash and
-   * insert the proposal data's hashcode into the same row.
-   *
-   * @param cls closure
-   * @param order_id alphanumeric string that uniquely identifies the proposal
-   * @param merchant_pub merchant's public key
-   * @param timestamp timestamp of this proposal data
-   * @param contract_terms proposal data to store
-   * @return transaction status
+   * Refund is now at or above the requested amount.
    */
-  enum GNUNET_DB_QueryStatus
-  (*insert_contract_terms)(void *cls,
-                           const char *order_id,
-                           const struct TALER_MerchantPublicKeyP *merchant_pub,
-                           struct GNUNET_TIME_Absolute timestamp,
-                           const json_t *contract_terms);
+  TALER_MERCHANTDB_RS_SUCCESS = 1
+
+};
+
+
+/**
+ * Function called with information about a wire transfer identifier.
+ *
+ * @param cls closure
+ * @param order_id the order to which the deposits belong
+ * @param deposit_value the amount deposited under @a order_id
+ * @param deposit_fee the fee charged for @a deposit_value
+ */
+typedef void
+(*TALER_MERCHANTDB_TransferSummaryCallback)(
+  void *cls,
+  const char *order_id,
+  const struct TALER_Amount *deposit_value,
+  const struct TALER_Amount *deposit_fee);
+
+
+/**
+ * Function called with detailed information about a wire transfer and
+ * the underlying deposits that are being aggregated.
+ *
+ * @param cls closure
+ * @param current_offset offset in the exchange reply we are at
+ * @param ttd details about the transfer at @a current_offset
+ */
+typedef void
+(*TALER_MERCHANTDB_TransferDetailsCallback)(
+  void *cls,
+  unsigned int current_offset,
+  const struct TALER_TrackTransferDetails *ttd);
+
+
+/**
+ * Function called with information about a wire transfer.
+ *
+ * @param cls closure with a `json_t *` array to build up the response
+ * @param credit_amount how much was wired to the merchant (minus fees)
+ * @param wtid wire transfer identifier
+ * @param payto_uri target account that received the wire transfer
+ * @param exchange_url base URL of the exchange that made the wire transfer
+ * @param transfer_serial_id serial number identifying the transfer in the 
backend
+ * @param execution_time when did the exchange make the transfer, 
#GNUNET_TIME_UNIT_FOREVER_ABS
+ *           if it did not yet happen
+ * @param verified true if we checked the exchange's answer and liked it,
+ *                 false there is a problem (verification failed or did not 
yet happen)
+ * @param confirmed true if the merchant confirmed this wire transfer
+ *                 false if it is so far only claimed to have been made by the 
exchange
+ */
+typedef void
+(*TALER_MERCHANTDB_TransferCallback)(
+  void *cls,
+  const struct TALER_Amount *credit_amount,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const char *payto_uri,
+  const char *exchange_url,
+  uint64_t transfer_serial_id,
+  struct GNUNET_TIME_Absolute execution_time,
+  bool verified,
+  bool confirmed);
+
+
+/**
+ * Callback with reserve details.
+ *
+ * @param cls closure
+ * @param reserve_pub public key of the reserve
+ * @param creation_time time when the reserve was setup
+ * @param expiration_time time when the reserve will be closed by the exchange
+ * @param merchant_initial_amount initial amount that the merchant claims to 
have filled the
+ *           reserve with
+ * @param exchange_initial_amount initial amount that the exchange claims to 
have received
+ * @param pickup_amount total of tips that were picked up from this reserve
+ * @param committed_amount total of tips that the merchant committed to, but 
that were not
+ *           picked up yet
+ * @param active true if the reserve is still active (we have the private key)
+ */
+typedef void
+(*TALER_MERCHANTDB_ReservesCallback)(
+  void *cls,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct GNUNET_TIME_Absolute creation_time,
+  struct GNUNET_TIME_Absolute expiration_time,
+  const struct TALER_Amount *merchant_initial_amount,
+  const struct TALER_Amount *exchange_initial_amount,
+  const struct TALER_Amount *pickup_amount,
+  const struct TALER_Amount *committed_amount,
+  bool active);
+
+
+/**
+ * Callback with details about a reserve pending exchange confirmation.
+ *
+ * @param cls closure
+ * @param instance_id for which instance is this reserve
+ * @param exchange_url base URL of the exchange
+ * @param reserve_pub public key of the reserve
+ * @param expected_amount how much do we expect to see in the reserve
+ */
+typedef void
+(*TALER_MERCHANTDB_PendingReservesCallback)(
+  void *cls,
+  const char *instance_id,
+  const char *exchange_url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *expected_amount);
+
 
+/**
+ * Details about a tip.
+ */
+struct TALER_MERCHANTDB_TipDetails
+{
   /**
-   * Mark contract terms as paid.  Needed by /history as only paid
-   * contracts must be shown.
-   *
-   * NOTE: we can't get the list of (paid) 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
+   * ID of the tip.
    */
-  enum GNUNET_DB_QueryStatus
-  (*mark_proposal_paid)(void *cls,
-                        const struct GNUNET_HashCode *h_contract_terms,
-                        const struct TALER_MerchantPublicKeyP *merchant_pub);
+  struct GNUNET_HashCode tip_id;
 
   /**
-   * Store the order ID that was used to pay for a resource within a session.
-   *
-   * @param cls closure
-   * @param session_id session id
-   * @param fulfillment_url URL that canonically identifies the resource
-   *        being paid for
-   * @param order_id the order ID that was used when paying for the resource 
URL
-   * @param merchant_pub public key of the merchant, identifying the instance
-   * @return transaction status
+   * Total amount of the tip.
    */
-  enum GNUNET_DB_QueryStatus
-  (*insert_session_info)(void *cls,
-                         const char *session_id,
-                         const char *fulfillment_url,
-                         const char *order_id,
-                         const struct TALER_MerchantPublicKeyP *merchant_pub);
+  struct TALER_Amount total_amount;
 
   /**
-   * Retrieve the order ID that was used to pay for a resource within a 
session.
-   *
+   * Reason given for granting the tip.
+   */
+  char *reason;
+};
+
+
+/**
+ * Function called with information about a coin that was deposited.
+ *
+ * @param cls closure
+ * @param deposit_serial which deposit operation is this about
+ * @param exchange_url URL of the exchange that issued the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param h_wire hash of merchant's wire details
+ * @param coin_pub public key of the coin
+ */
+typedef void
+(*TALER_MERCHANTDB_DepositedCoinsCallback)(
+  void *cls,
+  uint64_t deposit_serial,
+  const char *exchange_url,
+  const struct GNUNET_HashCode *h_wire,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub);
+
+
+/**
+ * Callback with reserve details.
+ *
+ * @param cls closure
+ * @param creation_time time when the reserve was setup
+ * @param expiration_time time when the reserve will be closed by the exchange
+ * @param merchant_initial_amount initial amount that the merchant claims to 
have filled the
+ *           reserve with
+ * @param exchange_initial_amount initial amount that the exchange claims to 
have received
+ * @param picked_up_amount total of tips that were picked up from this reserve
+ * @param committed_amount total of tips that the merchant committed to, but 
that were not
+ *           picked up yet
+ * @param active true if the reserve is still active (we have the private key)
+ * @param tips_length length of the @a tips array
+ * @param tips information about the tips created by this reserve
+ */
+typedef void
+(*TALER_MERCHANTDB_ReserveDetailsCallback)(
+  void *cls,
+  struct GNUNET_TIME_Absolute creation_time,
+  struct GNUNET_TIME_Absolute expiration_time,
+  const struct TALER_Amount *merchant_initial_amount,
+  const struct TALER_Amount *exchange_initial_amount,
+  const struct TALER_Amount *picked_up_amount,
+  const struct TALER_Amount *committed_amount,
+  bool active,
+  unsigned int tips_length,
+  const struct TALER_MERCHANTDB_TipDetails *tips);
+
+
+/**
+ * Typically called by `lookup_tips`.
+ *
+ * @param cls closure
+ * @param row_id row of the tip in the database
+ * @param tip_id id of the tip
+ * @param amount amount of the tip
+ */
+typedef void
+(*TALER_MERCHANTDB_TipsCallback)(void *cls,
+                                 uint64_t row_id,
+                                 struct GNUNET_HashCode tip_id,
+                                 struct TALER_Amount amount);
+
+
+/**
+ * Function called with information about a coin that was deposited.
+ *
+ * @param cls closure
+ * @param exchange_url URL of the exchange that issued the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee wire fee the exchange charges
+ * @param h_wire hash of merchant's wire details
+ * @param deposit_timestamp when did the exchange receive the deposit
+ * @param refund_deadline until when are refunds allowed
+ * @param exchange_sig signature by the exchange
+ * @param exchange_pub exchange signing key used for @a exchange_sig
+ */
+typedef void
+(*TALER_MERCHANTDB_CoinDepositCallback)(
+  void *cls,
+  const char *exchange_url,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_Amount *refund_fee,
+  const struct TALER_Amount *wire_fee,
+  const struct GNUNET_HashCode *h_wire,
+  struct GNUNET_TIME_Absolute deposit_timestamp,
+  struct GNUNET_TIME_Absolute refund_deadline,
+  const struct TALER_ExchangeSignatureP *exchange_sig,
+  const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+/**
+ * Details about a pickup operation executed by the merchant.
+ */
+struct TALER_MERCHANTDB_PickupDetails
+{
+  /**
+   * Identifier for the pickup operation.
+   */
+  struct GNUNET_HashCode pickup_id;
+
+  /**
+   * Total amount requested for this @e pickup_id.
+   */
+  struct TALER_Amount requested_amount;
+
+  /**
+   * Number of planchets involved in the request.
+   */
+  unsigned int num_planchets;
+
+};
+
+
+/**
+ * Handle to interact with the database.
+ *
+ * Functions ending with "_TR" run their OWN transaction scope
+ * and MUST NOT be called from within a transaction setup by the
+ * caller.  Functions ending with "_NT" require the caller to
+ * setup a transaction scope.  Functions without a suffix are
+ * simple, single SQL queries that MAY be used either way.
+ */
+struct TALER_MERCHANTDB_Plugin
+{
+
+  /**
+   * Closure for all callbacks.
+   */
+  void *cls;
+
+  /**
+   * Name of the library which generated this plugin.  Set by the
+   * plugin loader.
+   */
+  char *library_name;
+
+  /**
+   * Drop merchant tables. Used for testcases.
+   *
    * @param cls closure
-   * @param[out] order_id location to store the order ID that was used when
-   *             paying for the resource URL
-   * @param session_id session id
+   * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+   */
+  int
+  (*drop_tables) (void *cls);
+
+  /**
+   * Do a pre-flight check that we are not in an uncommitted transaction.
+   * If we are, try to commit the previous transaction and output a warning.
+   * Does not return anything, as we will continue regardless of the outcome.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   */
+  void
+  (*preflight) (void *cls);
+
+  /**
+   * Start a transaction.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param name unique name identifying the transaction (for debugging),
+   *             must point to a constant
+   * @return #GNUNET_OK on success
+   */
+  int
+  (*start) (void *cls,
+            const char *name);
+
+  /**
+   * Start a transaction with isolation level 'read committed'.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param name unique name identifying the transaction (for debugging),
+   *             must point to a constant
+   * @return #GNUNET_OK on success
+   */
+  int
+  (*start_read_committed) (void *cls,
+                           const char *name);
+
+  /**
+   * Roll back the current transaction of a database connection.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @return #GNUNET_OK on success
+   */
+  void
+  (*rollback) (void *cls);
+
+  /**
+   * Commit the current transaction of a database connection.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*commit)(void *cls);
+
+  /**
+   * Lookup all of the instances this backend has configured.
+   *
+   * @param cls closure
+   * @param active_only only find 'active' instances
+   * @param cb function to call on all instances found
+   * @param cb_cls closure for @a cb
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_instances)(void *cls,
+                      bool active_only,
+                      TALER_MERCHANTDB_InstanceCallback cb,
+                      void *cb_cls);
+
+  /**
+   * Insert information about an instance into our database.
+   *
+   * @param cls closure
+   * @param merchant_pub public key of the instance
+   * @param merchant_priv private key of the instance
+   * @param is details about the instance
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_instance)(void *cls,
+                     const struct TALER_MerchantPublicKeyP *merchant_pub,
+                     const struct TALER_MerchantPrivateKeyP *merchant_priv,
+                     const struct TALER_MERCHANTDB_InstanceSettings *is);
+
+  /**
+   * Insert information about an instance's account into our database.
+   *
+   * @param cls closure
+   * @param id identifier of the instance
+   * @param account_details details about the account
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_account)(
+    void *cls,
+    const char *id,
+    const struct TALER_MERCHANTDB_AccountDetails *account_details);
+
+  /**
+   * Delete private key of an instance from our database.
+   *
+   * @param cls closure
+   * @param merchant_id identifier of the instance
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*delete_instance_private_key)(
+    void *cls,
+    const char *merchant_id);
+
+  /**
+   * Purge an instance and all associated information from our database.
+   * Highly likely to cause undesired data loss. Use with caution.
+   *
+   * @param cls closure
+   * @param merchant_id identifier of the instance
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*purge_instance)(void *cls,
+                    const char *merchant_id);
+
+  /**
+   * Update information about an instance into our database.
+   *
+   * @param cls closure
+   * @param is details about the instance
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*update_instance)(void *cls,
+                     const struct TALER_MERCHANTDB_InstanceSettings *is);
+
+  /**
+   * Set an instance's account in our database to "inactive".
+   *
+   * @param cls closure
+   * @param h_wire hash of the wire account to set to inactive
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*inactivate_account)(void *cls,
+                        const struct GNUNET_HashCode *h_wire);
+
+  /**
+   * Lookup all of the products the given instance has configured.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup products for
+   * @param cb function to call on all products found
+   * @param cb_cls closure for @a cb
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_products)(void *cls,
+                     const char *instance_id,
+                     TALER_MERCHANTDB_ProductsCallback cb,
+                     void *cb_cls);
+
+  /**
+   * Lookup details about a particular product.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup products for
+   * @param product_id product to lookup
+   * @param[out] pd set to the product details on success, can be NULL
+   *             (in that case we only want to check if the product exists)
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_product)(void *cls,
+                    const char *instance_id,
+                    const char *product_id,
+                    struct TALER_MERCHANTDB_ProductDetails *pd);
+
+  /**
+   * Delete information about a product. Note that the transaction must
+   * enforce that no stocks are currently locked.
+   *
+   * @param cls closure
+   * @param instance_id instance to delete product of
+   * @param product_id product to delete
+   * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+   *           if locks prevent deletion OR product unknown
+   */
+  enum GNUNET_DB_QueryStatus
+  (*delete_product)(void *cls,
+                    const char *instance_id,
+                    const char *product_id);
+
+  /**
+   * Insert details about a particular product.
+   *
+   * @param cls closure
+   * @param instance_id instance to insert product for
+   * @param product_id product identifier of product to insert
+   * @param pd the product details to insert
+   * @return database result code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_product)(void *cls,
+                    const char *instance_id,
+                    const char *product_id,
+                    const struct TALER_MERCHANTDB_ProductDetails *pd);
+
+  /**
+   * Update details about a particular product. Note that the
+   * transaction must enforce that the sold/stocked/lost counters
+   * are not reduced (i.e. by expanding the WHERE clause on the existing
+   * values).
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup products for
+   * @param product_id product to lookup
+   * @param pd set to the product details on success, can be NULL
+   *             (in that case we only want to check if the product exists);
+   *             total_sold in @a pd is ignored, total_lost must not
+   *             exceed total_stock minus the existing total_sold;
+   *             total_sold and total_stock must be larger or equal to
+   *             the existing value;
+   * @return database result code, #GNUNET_DB_SUCCESS_NO_RESULTS if the
+   *         non-decreasing constraints are not met *or* if the product
+   *         does not yet exist.
+   */
+  enum GNUNET_DB_QueryStatus
+  (*update_product)(void *cls,
+                    const char *instance_id,
+                    const char *product_id,
+                    const struct TALER_MERCHANTDB_ProductDetails *pd);
+
+  /**
+   * Lock stocks of a particular product. Note that the transaction must
+   * enforce that the "stocked-sold-lost >= locked" constraint holds.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup products for
+   * @param product_id product to lookup
+   * @param uuid the UUID that holds the lock
+   * @param quantity how many units should be locked
+   * @param expiration_time when should the lock expire
+   * @return database result code, #GNUNET_DB_SUCCESS_NO_RESULTS if the
+   *         product is unknown OR if there insufficient stocks remaining
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lock_product)(void *cls,
+                  const char *instance_id,
+                  const char *product_id,
+                  const struct GNUNET_Uuid *uuid,
+                  uint64_t quantity,
+                  struct GNUNET_TIME_Absolute expiration_time);
+
+
+  /**
+   * Delete information about an order. Note that the transaction must
+   * enforce that the order is not awaiting payment anymore.
+   *
+   * @param cls closure
+   * @param instance_id instance to delete order of
+   * @param order_id order to delete
+   * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+   *           if locks prevent deletion OR order unknown
+   */
+  enum GNUNET_DB_QueryStatus
+  (*delete_order)(void *cls,
+                  const char *instance_id,
+                  const char *order_id);
+
+
+  /**
+   * Retrieve order given its @a order_id and the @a instance_id.
+   *
+   * @param cls closure
+   * @param instance_id instance to obtain order of
+   * @param order_id order id used to perform the lookup
+   * @param[out] contract_terms where to store the retrieved contract terms,
+   *             NULL to only test if the order exists
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_order)(void *cls,
+                  const char *instance_id,
+                  const char *order_id,
+                  json_t **contract_terms);
+
+
+  /**
+   * Retrieve order summary given its @a order_id and the @a instance_id.
+   *
+   * @param cls closure
+   * @param instance_id instance to obtain order of
+   * @param order_id order id used to perform the lookup
+   * @param[out] timestamp when was the order created
+   * @param[out] order_serial under which serial do we keep this order
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_order_summary)(void *cls,
+                          const char *instance_id,
+                          const char *order_id,
+                          struct GNUNET_TIME_Absolute *timestamp,
+                          uint64_t *order_serial);
+
+
+  /**
+   * Retrieve orders given the @a instance_id.
+   *
+   * @param cls closure
+   * @param instance_id instance to obtain order of
+   * @param of filter to apply when looking up orders
+   * @param[out] contract_terms where to store the retrieved contract terms,
+   *             NULL to only test if the order exists
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_orders)(void *cls,
+                   const char *instance_id,
+                   const struct TALER_MERCHANTDB_OrderFilter *of,
+                   TALER_MERCHANTDB_OrdersCallback cb,
+                   void *cb_cls);
+
+
+  /**
+   * Insert order into db.
+   *
+   * @param cls closure
+   * @param instance_id identifies the instance responsible for the order
+   * @param order_id alphanumeric string that uniquely identifies the order
+   * @param pay_deadline how long does the customer have to pay for the order
+   * @param contract_terms proposal data to store
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_order)(void *cls,
+                  const char *instance_id,
+                  const char *order_id,
+                  struct GNUNET_TIME_Absolute pay_deadline,
+                  const json_t *contract_terms);
+
+
+  /**
+   * Release an inventory lock by UUID. Releases ALL stocks locked under
+   * the given UUID.
+   *
+   * @param cls closure
+   * @param uuid the UUID to release locks for
+   * @return transaction status,
+   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a 
uuid
+   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+   */
+  enum GNUNET_DB_QueryStatus
+  (*unlock_inventory)(void *cls,
+                      const struct GNUNET_Uuid *uuid);
+
+
+  /**
+   * Lock inventory stock to a particular order.
+   *
+   * @param cls closure
+   * @param instance_id identifies the instance responsible for the order
+   * @param order_id alphanumeric string that uniquely identifies the order
+   * @param product_id uniquely identifies the product to be locked
+   * @param quantity how many units should be locked to the @a order_id
+   * @return transaction status,
+   *   #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks
+   *   #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_order_lock)(void *cls,
+                       const char *instance_id,
+                       const char *order_id,
+                       const char *product_id,
+                       uint64_t quantity);
+
+
+  /**
+   * Retrieve contract terms given its @a order_id
+   *
+   * @param cls closure
+   * @param instance_id instance's identifier
+   * @param order_id order_id used to lookup.
+   * @param[out] contract_terms where to store the result, NULL to only check 
for existence
+   * @param[out] order_serial set to the order's serial number
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_contract_terms)(void *cls,
+                           const char *instance_id,
+                           const char *order_id,
+                           json_t **contract_terms,
+                           uint64_t *order_serial);
+
+
+  /**
+   * Store contract terms given its @a order_id. Note that some attributes are
+   * expected to be calculated inside of the function, like the hash of the
+   * contract terms (to be hashed), the creation_time and pay_deadline (to be
+   * obtained from the merchant_orders table). The "session_id" should be
+   * initially set to the empty string.  The "fulfillment_url" and 
"refund_deadline"
+   * must be extracted from @a contract_terms.
+   *
+   * @param cls closure
+   * @param instance_id instance's identifier
+   * @param order_id order_id used to store
+   * @param contract_terms contract to store
+   * @return transaction status, #GNUNET_DB_STATUS_HARD_ERROR if @a 
contract_terms
+   *          is malformed
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_contract_terms)(void *cls,
+                           const char *instance_id,
+                           const char *order_id,
+                           json_t *contract_terms);
+
+
+  /**
+   * Delete information about a contract. Note that the transaction must
+   * enforce that the contract is not awaiting payment anymore AND was not
+   * paid, or is past the legal expiration.
+   *
+   * @param cls closure
+   * @param instance_id instance to delete order of
+   * @param order_id order to delete
+   * @param legal_expiration how long do we need to keep (paid) contracts on
+   *          file for legal reasons (i.e. taxation)
+   * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+   *           if locks prevent deletion OR order unknown
+   */
+  enum GNUNET_DB_QueryStatus
+  (*delete_contract_terms)(void *cls,
+                           const char *instance_id,
+                           const char *order_id,
+                           struct GNUNET_TIME_Relative legal_expiration);
+
+
+  /**
+   * Lookup information about coins that were successfully deposited for a
+   * given contract.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup deposits for
+   * @param h_contract_terms proposal data's hashcode
+   * @param cb function to call with payment data
+   * @param cb_cls closure for @a cb
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_deposits)(void *cls,
+                     const char *instance_id,
+                     const struct GNUNET_HashCode *h_contract_terms,
+                     TALER_MERCHANTDB_DepositsCallback cb,
+                     void *cb_cls);
+
+
+  /**
+   * Insert an exchange signing key into our database.
+   *
+   * @param cls closure
+   * @param master_pub exchange master public key used for @a master_sig
+   * @param exchange_pub exchange signing key to insert
+   * @param start_date when does the signing key become valid
+   * @param expire_date when does the signing key stop being used
+   * @param end_date when does the signing key become void as proof
+   * @param master_sig signature of @a master_pub over the @a exchange_pub and 
the dates
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_exchange_signkey)(
+    void *cls,
+    const struct TALER_MasterPublicKeyP *master_pub,
+    const struct TALER_ExchangePublicKeyP *exchange_pub,
+    struct GNUNET_TIME_Absolute start_date,
+    struct GNUNET_TIME_Absolute expire_date,
+    struct GNUNET_TIME_Absolute end_date,
+    const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Insert payment confirmation from the exchange into the database.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup deposits for
+   * @param deposit_timestamp time when the exchange generated the deposit 
confirmation
+   * @param h_contract_terms proposal data's hashcode
+   * @param coin_pub public key of the coin
+   * @param exchange_url URL of the exchange that issued @a coin_pub
+   * @param amount_with_fee amount the exchange will deposit for this coin
+   * @param deposit_fee fee the exchange will charge for this coin
+   * @param wire_fee wire fee the exchange charges
+   * @param h_wire hash of the wire details of the target account of the 
merchant
+   * @param exchange_sig signature from exchange that coin was accepted
+   * @param exchange_pub signgin key that was used for @a exchange_sig
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_deposit)(void *cls,
+                    const char *instance_id,
+                    struct GNUNET_TIME_Absolute deposit_timestamp,
+                    const struct GNUNET_HashCode *h_contract_terms,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
+                    const struct TALER_Amount *amount_with_fee,
+                    const struct TALER_Amount *deposit_fee,
+                    const struct TALER_Amount *refund_fee,
+                    const struct TALER_Amount *wire_fee,
+                    const struct GNUNET_HashCode *h_wire,
+                    const struct TALER_ExchangeSignatureP *exchange_sig,
+                    const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+  /**
+   * Obtain refunds associated with a contract.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param instance_id instance to lookup refunds for
+   * @param h_contract_terms hash code of the contract
+   * @param rc function to call for each coin on which there is a refund
+   * @param rc_cls closure for @a rc
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_refunds)(void *cls,
+                    const char *instance_id,
+                    const struct GNUNET_HashCode *h_contract_terms,
+                    TALER_MERCHANTDB_RefundCallback rc,
+                    void *rc_cls);
+
+
+  /**
+   * Mark contract as paid and store the current @a session_id
+   * for which the contract was paid. Deletes the underlying order
+   * and marks the locked stocks of the order as sold.
+   *
+   * @param cls closure
+   * @param instance_id instance to mark contract as paid for
+   * @param h_contract_terms hash of the contract that is now paid
+   * @param session_id the session that paid the contract, can be NULL
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*mark_contract_paid)(void *cls,
+                        const char *instance_id,
+                        const struct GNUNET_HashCode *h_contract_terms,
+                        const char *session_id);
+
+
+  /**
+   * Function called during aborts to refund a coin. Marks the
+   * respective coin as refunded.
+   *
+   * @param cls closure
+   * @param instance_id instance to refund payment for
+   * @param h_contract_terms hash of the contract to refund coin for
+   * @param refund_timestamp timestamp of when the coin was refunded
+   * @param coin_pub public key of the coin to refund (fully)
+   * @param reason text justifying the refund
+   * @return transaction status
+   *        #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a coin_pub is unknown to 
us;
+   *        #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
+   *        regardless of whether it actually increased the refund
+   */
+  enum GNUNET_DB_QueryStatus
+  (*refund_coin)(void *cls,
+                 const char *instance_id,
+                 const struct GNUNET_HashCode *h_contract_terms,
+                 struct GNUNET_TIME_Absolute refund_timestamp,
+                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                 const char *reason);
+
+
+  /**
+   * Retrieve contract terms given its @a order_id
+   *
+   * @param cls closure
+   * @param instance_id instance's identifier
+   * @param order_id order to lookup contract for
+   * @param[out] h_contract_terms set to the hash of the contract.
+   * @param[out] paid set to the payment status of the contract
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_order_status)(void *cls,
+                         const char *instance_id,
+                         const char *order_id,
+                         struct GNUNET_HashCode *h_contract_terms,
+                         bool *paid);
+
+
+  /**
+   * Retrieve payment and wire status for a given @a order_serial and
+   * session ID.
+   *
+   * @param cls closure
+   * @param order_serial identifies the order
+   * @param session_id session for which to check the payment status, NULL for 
any
+   * @param[out] paid set to the payment status of the contract
+   * @param[out] wired set to the wire transfer status of the exchange payment
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_payment_status)(void *cls,
+                           uint64_t order_serial,
+                           const char *session_id,
+                           bool *paid,
+                           bool *wired);
+
+
+  /**
+   * Retrieve details about coins that were deposited for an order.
+   *
+   * @param cls closure
+   * @param order_serial identifies the order
+   * @param cb function to call for each deposited coin
+   * @param cb_cls closure for @a cb
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_deposits_by_order)(void *cls,
+                              uint64_t order_serial,
+                              TALER_MERCHANTDB_DepositedCoinsCallback cb,
+                              void *cb_cls);
+
+
+  /**
+   * Retrieve wire transfer details for all deposits associated with
+   * a given @a order_serial.
+   *
+   * @param cls closure
+   * @param order_serial identifies the order
+   * @param cb function called with the wire transfer details
+   * @param cb_cls closure for @a cb
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_transfer_details_by_order)(
+    void *cls,
+    uint64_t order_serial,
+    TALER_MERCHANTDB_OrderTransferDetailsCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Insert wire transfer details for a deposit.
+   *
+   * @param cls closure
+   * @param deposit_serial serial number of the deposit
+   * @param dd deposit transfer data from the exchange to store
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_deposit_to_transfer)(void *cls,
+                                uint64_t deposit_serial,
+                                const struct TALER_EXCHANGE_DepositData *dd);
+
+
+  /**
+   * Set 'wired' status for an order to 'true'.
+   *
+   * @param cls closure
+   * @param order_serial serial number of the order
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*mark_order_wired)(void *cls,
+                      uint64_t order_serial);
+
+
+  /**
+   * Function called when some backoffice staff decides to award or
+   * increase the refund on an existing contract.  This function
+   * MUST be called from within a transaction scope setup by the
+   * caller as it executes multiple SQL statements.
+   *
+   * @param cls closure
+   * @param instance_id instance identifier
+   * @param order_id the order to increase the refund for
+   * @param refund maximum refund to return to the customer for this contract
+   * @param reason 0-terminated UTF-8 string giving the reason why the customer
+   *               got a refund (free form, business-specific)
+   * @return transaction status
+   *        #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the 
amount we
+   *        were originally paid and thus the transaction failed;
+   *        #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
+   *        regardless of whether it actually increased the refund beyond
+   *        what was already refunded (idempotency!)
+   */
+  enum TALER_MERCHANTDB_RefundStatus
+  (*increase_refund)(void *cls,
+                     const char *instance_id,
+                     const char *order_id,
+                     const struct TALER_Amount *refund,
+                     const char *reason);
+
+
+  /**
+   * Obtain detailed refund data associated with a contract.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param instance_id instance to lookup refunds for
+   * @param h_contract_terms hash code of the contract
+   * @param rc function to call for each coin on which there is a refund
+   * @param rc_cls closure for @a rc
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_refunds_detailed)(void *cls,
+                             const char *instance_id,
+                             const struct GNUNET_HashCode *h_contract_terms,
+                             TALER_MERCHANTDB_RefundDetailCallback rc,
+                             void *rc_cls);
+
+  /**
+   * Insert refund proof data from the exchange into the database.
+   *
+   * @param cls closure
+   * @param refund_serial serial number of the refund
+   * @param refund_fee refund fee the exchange said it charged
+   * @param exchange_sig signature from exchange that coin was refunded
+   * @param exchange_pub signing key that was used for @a exchange_sig
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_refund_proof)(void *cls,
+                         uint64_t refund_serial,
+                         const struct TALER_Amount *refund_fee,
+                         const struct TALER_ExchangeSignatureP *exchange_sig,
+                         const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+  /**
+   * Lookup refund proof data.
+   *
+   * @param cls closure
+   * @param refund_serial serial number of the refund
+   * @param[out] exchange_sig set to signature from exchange
+   * @param[out] exchange_pub signing key that was used for @a exchange_sig
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_refund_proof)(void *cls,
+                         uint64_t refund_serial,
+                         struct TALER_ExchangeSignatureP *exchange_sig,
+                         struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+  /**
+   * Retrieve the order ID that was used to pay for a resource within a 
session.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup the order from
    * @param fulfillment_url URL that canonically identifies the resource
    *        being paid for
-   * @param merchant_pub public key of the merchant, identifying the instance
+   * @param session_id session id
+   * @param[out] order_id location to store the order ID that was used when
+   *             paying for the resource URL
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_session_info)(void *cls,
-                       char **order_id,
-                       const char *session_id,
-                       const char *fulfillment_url,
-                       const struct TALER_MerchantPublicKeyP *merchant_pub);
+  (*lookup_order_by_fulfillment)(void *cls,
+                                 const char *instance_id,
+                                 const char *fulfillment_url,
+                                 const char *session_id,
+                                 char **order_id);
 
   /**
-   * Retrieve proposal data given its order ID.
+   * Insert information about a wire transfer the merchant has received.
    *
    * @param cls closure
-   * @param[out] contract_terms where to store the result
-   * @param order_id order_id used to lookup.
-   * @param merchant_pub instance's public key.
+   * @param instance_id instance to lookup the order from
+   * @param exchange_url which exchange made the transfer
+   * @param wtid identifier of the wire transfer
+   * @param credit_amount how much did we receive
+   * @param payto_uri what is the merchant's bank account that received the 
transfer
+   * @param confirmed whether the transfer was confirmed by the merchant or
+   *                  was merely claimed by the exchange at this point
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_contract_terms)(void *cls,
-                         json_t **contract_terms,
-                         const char *order_id,
-                         const struct TALER_MerchantPublicKeyP *merchant_pub);
+  (*insert_transfer)(
+    void *cls,
+    const char *instance_id,
+    const char *exchange_url,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    const struct TALER_Amount *credit_amount,
+    const char *payto_uri,
+    bool confirmed);
+
 
   /**
-   * Retrieve order given its order id and the instance's merchant public key.
+   * Lookup account serial by payto URI.
    *
    * @param cls closure
-   * @param[out] contract_terms where to store the retrieved contract terms
-   * @param order id order id used to perform the lookup
-   * @param merchant_pub merchant public key that identifies the instance
+   * @param instance_id instance to lookup the account from
+   * @param payto_uri what is the merchant's bank account to lookup
+   * @param[out] account_serial serial number of the account
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_order)(void *cls,
-                json_t **contract_terms,
-                const char *order_id,
-                const struct TALER_MerchantPublicKeyP *merchant_pub);
+  (*lookup_account)(void *cls,
+                    const char *instance_id,
+                    const char *payto_uri,
+                    uint64_t *account_serial);
 
 
   /**
-   * Retrieve proposal data given its hashcode
+   * Insert information about a wire transfer the merchant has received.
    *
    * @param cls closure
-   * @param[out] contract_terms where to store the result
-   * @param h_contract_terms hashcode used to lookup.
-   * @param merchant_pub instance's public key.
+   * @param instance_id instance to provide transfer details for
+   * @param exchange_url which exchange made the transfer
+   * @param payto_uri what is the merchant's bank account that received the 
transfer
+   * @param wtid identifier of the wire transfer
+   * @param td transfer details to store
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_contract_terms_from_hash)(
+  (*insert_transfer_details)(
     void *cls,
-    json_t **contract_terms,
-    const struct GNUNET_HashCode *h_contract_terms,
-    const struct TALER_MerchantPublicKeyP *merchant_pub);
+    const char *instance_id,
+    const char *exchange_url,
+    const char *payto_uri,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    const struct TALER_EXCHANGE_TransferData *td);
 
 
   /**
-   * Retrieve paid contract terms data given its hashcode.
+   * Obtain information about wire fees charged by an exchange,
+   * including signature (so we have proof).
    *
    * @param cls closure
-   * @param[out] contract_terms where to store the result
-   * @param h_contract_terms hashcode used to lookup.
-   * @param merchant_pub instance's public key.
-   * @return transaction status
+   * @param master_pub master public key of the exchange
+   * @param h_wire_method hash of wire method
+   * @param contract_date date of the contract to use for the lookup
+   * @param[out] wire_fee wire fee charged
+   * @param[out] closing_fee closing fee charged (irrelevant for us,
+   *              but needed to check signature)
+   * @param[out] start_date start of fee being used
+   * @param[out] end_date end of fee being used
+   * @param[out] master_sig signature of exchange over fee structure
+   * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*find_paid_contract_terms_from_hash)(
-    void *cls,
-    json_t **contract_terms,
-    const struct GNUNET_HashCode *h_contract_terms,
-    const struct TALER_MerchantPublicKeyP *merchant_pub);
+  (*lookup_wire_fee)(void *cls,
+                     const struct TALER_MasterPublicKeyP *master_pub,
+                     const char *wire_method,
+                     struct GNUNET_TIME_Absolute contract_date,
+                     struct TALER_Amount *wire_fee,
+                     struct TALER_Amount *closing_fee,
+                     struct GNUNET_TIME_Absolute *start_date,
+                     struct GNUNET_TIME_Absolute *end_date,
+                     struct TALER_MasterSignatureP *master_sig);
 
 
   /**
-   * Return proposals whose timestamps are younger than `date`.
-   * Among those proposals, only those ones being between the
-   * start-th and (start-nrows)-th record are returned.  The rows
-   * are sorted having the youngest first.
+   * Lookup information about coin payments by @a h_contract_terms and
+   * @a coin_pub.
    *
-   * @param cls our plugin handle.
-   * @param date only results younger than this date are returned.
-   * @param merchant_pub instance's public key; only rows related to this
-   * instance are returned.
-   * @param start only rows with serial id less than start are returned.
-   * @param nrows only nrows rows are returned.
-   * @param past if set to #GNUNET_YES, retrieves rows older than `date`.
-   * @param ascending if GNUNET_YES, then results will be sorted with youngest 
first.
-   * This is typically used to show live updates on the merchant's backoffice
-   * @param cb function to call with transaction data, can be NULL.
+   * @param cls closure
+   * @param instance_id instance to lookup payments for
+   * @param h_contract_terms proposal data's hashcode
+   * @param coin_pub public key to use for the search
+   * @param cb function to call with payment data
    * @param cb_cls closure for @a cb
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_contract_terms_by_date_and_range)(
+  (*lookup_deposits_by_contract_and_coin)(
     void *cls,
-    struct GNUNET_TIME_Absolute date,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    uint64_t start,
-    uint64_t nrows,
-    int past,
-    unsigned int ascending,
-    TALER_MERCHANTDB_ProposalDataCallback cb,
+    const char *instance_id,
+    const struct GNUNET_HashCode *h_contract_terms,
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    TALER_MERCHANTDB_CoinDepositCallback cb,
     void *cb_cls);
 
+
   /**
-   * Lookup for a proposal, respecting the signature used by the
-   * /history's db methods.
+   * Lookup transfer status.
    *
-   * @param cls db plugin handle
-   * @param order_id order id used to search for the proposal data
-   * @param merchant_pub public key of the merchant using this method
-   * @param cb the callback
-   * @param cb_cls closure to pass to @a cb
+   * @param cls closure
+   * @param exchange_url the exchange that made the transfer
+   * @param payto_uri account that received the transfer
+   * @param wtid wire transfer subject
+   * @param[out] total_amount amount that was debited from our
+   *             aggregate balance at the exchange (in total, sum of
+   *             the wire transfer amount and the @a wire_fee)
+   * @param[out] wire_fee the wire fee the exchange charged
+   * @param[out] execution_time when the transfer was executed by the exchange
+   * @param[out] verified did we confirm the transfer was OK
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_contract_terms_history)(
+  (*lookup_transfer)(
     void *cls,
-    const char *order_id,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    TALER_MERCHANTDB_ProposalDataCallback cb,
-    void *cb_cls);
+    const char *exchange_url,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    struct TALER_Amount *total_amount,
+    struct TALER_Amount *wire_fee,
+    struct GNUNET_TIME_Absolute *execution_time,
+    bool *verified);
 
 
   /**
-   * Return proposals whose timestamp are older than `date`.
-   * The rows are sorted having the youngest first.*
+   * Set transfer status to verified.
    *
-   * @param cls our plugin handle.
-   * @param date only results older than this date are returned.
-   * @param merchant_pub instance's public key; only rows related to this
-   * instance are returned.
-   * @param nrows only nrows rows are returned.
-   * @param cb function to call with transaction data, can be NULL.
-   * @param cb_cls closure for @a cb
+   * @param cls closure
+   * @param instance_id instance to lookup payments for
+   * @param exchange_url the exchange that made the transfer
+   * @param payto_uri account that received the transfer
+   * @param wtid wire transfer subject
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_contract_terms_by_date)(
+  (*set_transfer_status_to_verified)(
     void *cls,
-    struct GNUNET_TIME_Absolute date,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    uint64_t nrows,
-    TALER_MERCHANTDB_ProposalDataCallback cb,
-    void *cb_cls);
+    const char *exchange_url,
+    const struct TALER_WireTransferIdentifierRawP *wtid);
 
 
   /**
-   * Insert payment confirmation from the exchange into the database.
+   * Lookup transfer summary (used if we already verified the details).
    *
    * @param cls closure
-   * @param h_contract_terms proposal data's hashcode
-   * @param merchant_pub merchant's public key
-   * @param coin_pub public key of the coin
-   * @param exchange_url URL of the exchange that issued @a coin_pub
-   * @param amount_with_fee amount the exchange will deposit for this coin
-   * @param deposit_fee fee the exchange will charge for this coin
-   * @param wire_fee wire fee the exchange charges
-   * @param signkey_pub public key used by the exchange for @a exchange_proof
-   * @param exchange_proof proof from exchange that coin was accepted
+   * @param instance_id instance to lookup payments for
+   * @param exchange_url the exchange that made the transfer
+   * @param payto_uri account that received the transfer
+   * @param wtid wire transfer subject
+   * @param cb function to call with detailed transfer data
+   * @param cb_cls closure for @a cb
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*store_deposit)(void *cls,
-                   const struct GNUNET_HashCode *h_contract_terms,
-                   const struct TALER_MerchantPublicKeyP *merchant_pub,
-                   const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                   const char *exchange_url,
-                   const struct TALER_Amount *amount_with_fee,
-                   const struct TALER_Amount *deposit_fee,
-                   const struct TALER_Amount *refund_fee,
-                   const struct TALER_Amount *wire_fee,
-                   const struct TALER_ExchangePublicKeyP *signkey_pub,
-                   const json_t *exchange_proof);
+  (*lookup_transfer_summary)(
+    void *cls,
+    const char *exchange_url,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    TALER_MERCHANTDB_TransferSummaryCallback cb,
+    void *cb_cls);
 
 
   /**
-   * Insert mapping of @a coin_pub and @a h_contract_terms to
-   * corresponding @a wtid.
+   * Lookup transfer details. Used if we still need to verify the details.
    *
    * @param cls closure
-   * @param h_contract_terms proposal data's hashcode
-   * @param coin_pub public key of the coin
-   * @param wtid identifier of the wire transfer in which the exchange
-   *             send us the money for the coin deposit
+   * @param instance_id instance to lookup payments for
+   * @param exchange_url the exchange that made the transfer
+   * @param payto_uri account that received the transfer
+   * @param wtid wire transfer subject
+   * @param cb function to call with detailed transfer data
+   * @param cb_cls closure for @a cb
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*store_coin_to_transfer)(
+  (*lookup_transfer_details)(
     void *cls,
-    const struct GNUNET_HashCode *h_contract_terms,
-    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-    const struct TALER_WireTransferIdentifierRawP *wtid);
+    const char *exchange_url,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    TALER_MERCHANTDB_TransferDetailsCallback cb,
+    void *cb_cls);
 
 
   /**
-   * Insert wire transfer confirmation from the exchange into the database.
+   * Lookup transfers.
    *
    * @param cls closure
-   * @param exchange_url from which exchange did we get the @a exchange_proof
-   * @param wtid identifier of the wire transfer
-   * @param execution_time when was @a wtid executed
-   * @param signkey_pub public key used by the exchange for @a exchange_proof
-   * @param exchange_proof proof from exchange about what the deposit was for
+   * @param instance_id instance to lookup payments for
+   * @param payto_uri account that we are interested in transfers to
+   * @param before timestamp for the earliest transfer we care about
+   * @param after timestamp for the last transfer we care about
+   * @param limit number of entries to return, negative for descending in 
execution time,
+   *                positive for ascending in execution time
+   * @param offset transfer_serial number of the transfer we want to offset 
from
+   * @param verified filter transfers by verification status
+   * @param cb function to call with detailed transfer data
+   * @param cb_cls closure for @a cb
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*store_transfer_to_proof)(
-    void *cls,
-    const char *exchange_url,
-    const struct TALER_WireTransferIdentifierRawP *wtid,
-    struct GNUNET_TIME_Absolute execution_time,
-    const struct TALER_ExchangePublicKeyP *signkey_pub,
-    const json_t *exchange_proof);
+  (*lookup_transfers)(void *cls,
+                      const char *instance_id,
+                      const char *payto_uri,
+                      struct GNUNET_TIME_Absolute before,
+                      struct GNUNET_TIME_Absolute after,
+                      int64_t limit,
+                      uint64_t offset,
+                      enum TALER_EXCHANGE_YesNoAll yna,
+                      TALER_MERCHANTDB_TransferCallback cb,
+                      void *cb_cls);
 
 
   /**
@@ -516,386 +1687,318 @@ struct TALER_MERCHANTDB_Plugin
 
 
   /**
-   * Lookup information about coin payments by proposal data's hashcode.
+   * 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 now exists.  This has to happen before tips can be
+   * authorized.
    *
-   * @param cls closure
-   * @param h_contract_terms proposal data's hashcode
-   * @param merchant_pub merchant's public key. It's AND'd with @a 
h_contract_terms
-   *        in order to find the result.
-   * @param cb function to call with payment data
-   * @param cb_cls closure for @a cb
-   * @return transaction status
+   * @param cls closure, typically a connection to the db
+   * @param instance_id which instance is the reserve tied to
+   * @param reserve_priv which reserve is topped up or created
+   * @param reserve_pub which reserve is topped up or created
+   * @param exchange_url what URL is the exchange reachable at where the 
reserve is located
+   * @param initial_balance how much money will be added to the reserve
+   * @param expiration when does the reserve expire?
+   * @return transaction status, usually
+   *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
    */
-  enum GNUNET_DB_QueryStatus
-  (*find_payments)(void *cls,
-                   const struct GNUNET_HashCode *h_contract_terms,
-                   const struct TALER_MerchantPublicKeyP *merchant_pub,
-                   TALER_MERCHANTDB_CoinDepositCallback cb,
-                   void *cb_cls);
+  enum TALER_ErrorCode
+  (*insert_reserve)(void *cls,
+                    const char *instance_id,
+                    const struct TALER_ReservePrivateKeyP *reserve_priv,
+                    const struct TALER_ReservePublicKeyP *reserve_pub,
+                    const char *exchange_url,
+                    const struct TALER_Amount *initial_balance,
+                    struct GNUNET_TIME_Absolute expiration);
 
 
   /**
-   * Lookup information about coin payments by h_contract_terms and coin.
+   * Confirms @a credit as the amount the exchange claims to have received and
+   * thus really 'activates' the reserve.  This has to happen before tips can
+   * be authorized.
    *
-   * @param cls closure
-   * @param h_contract_terms proposal data's hashcode
-   * @param merchant_pub merchant's public key. It's AND'd with @a 
h_contract_terms
-   *        in order to find the result.
-   * @param coin_pub public key to use for the search
-   * @param cb function to call with payment data
-   * @param cb_cls closure for @a cb
-   * @return transaction status
+   * @param cls closure, typically a connection to the db
+   * @param instance_id which instance is the reserve tied to
+   * @param reserve_pub which reserve is topped up or created
+   * @param initial_exchange_balance how much money was be added to the reserve
+   *           according to the exchange
+   * @return transaction status, usually
+   *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
    */
   enum GNUNET_DB_QueryStatus
-  (*find_payments_by_hash_and_coin)(
-    void *cls,
-    const struct GNUNET_HashCode *h_contract_terms,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-    TALER_MERCHANTDB_CoinDepositCallback cb,
-    void *cb_cls);
+  (*activate_reserve)(void *cls,
+                      const char *instance_id,
+                      const struct TALER_ReservePublicKeyP *reserve_pub,
+                      const struct TALER_Amount *initial_exchange_balance);
 
 
   /**
-   * Lookup information about a transfer by @a h_contract_terms.  Note
-   * that in theory there could be multiple wire transfers for a
-   * single @a h_contract_terms, as the transaction may have involved
-   * multiple coins and the coins may be spread over different wire
-   * transfers.
+   * Lookup reserves.
    *
    * @param cls closure
-   * @param h_contract_terms proposal data's hashcode
-   * @param cb function to call with transfer data
+   * @param instance_id instance to lookup payments for
+   * @param created_after filter by reserves created after this date
+   * @param active filter by active reserves
+   * @param failures filter by reserves with a disagreement on the initial 
balance
+   * @param cb function to call with reserve summary data
    * @param cb_cls closure for @a cb
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_transfers_by_hash)(void *cls,
-                            const struct GNUNET_HashCode *h_contract_terms,
-                            TALER_MERCHANTDB_TransferCallback cb,
-                            void *cb_cls);
+  (*lookup_reserves)(void *cls,
+                     const char *instance_id,
+                     struct GNUNET_TIME_Absolute created_after,
+                     enum TALER_EXCHANGE_YesNoAll active,
+                     enum TALER_EXCHANGE_YesNoAll failures,
+                     TALER_MERCHANTDB_ReservesCallback cb,
+                     void *cb_cls);
 
 
   /**
-   * Lookup information about a coin deposits by @a wtid.
+   * Lookup reserves pending activation across all instances.
    *
    * @param cls closure
-   * @param wtid wire transfer identifier to find matching transactions for
-   * @param cb function to call with payment data
+   * @param cb function to call with reserve data
    * @param cb_cls closure for @a cb
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_deposits_by_wtid)(void *cls,
-                           const struct TALER_WireTransferIdentifierRawP *wtid,
-                           TALER_MERCHANTDB_CoinDepositCallback cb,
-                           void *cb_cls);
+  (*lookup_pending_reserves)(void *cls,
+                             TALER_MERCHANTDB_PendingReservesCallback cb,
+                             void *cb_cls);
 
 
   /**
-   * Lookup proof information about a wire transfer.
+   * Lookup reserve details.
    *
    * @param cls closure
-   * @param exchange_url from which exchange are we looking for proof
-   * @param wtid wire transfer identifier for the search
-   * @param cb function to call with proof data
+   * @param instance_id instance to lookup payments for
+   * @param reserve_pub public key of the reserve to inspect
+   * @param fetch_tips if true, also return information about tips
+   * @param cb function to call with reserve summary data
    * @param cb_cls closure for @a cb
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*find_proof_by_wtid)(void *cls,
-                        const char *exchange_url,
-                        const struct TALER_WireTransferIdentifierRawP *wtid,
-                        TALER_MERCHANTDB_ProofCallback cb,
-                        void *cb_cls);
+  (*lookup_reserve)(void *cls,
+                    const char *instance_id,
+                    const struct TALER_ReservePublicKeyP *reserve_pub,
+                    bool fetch_tips,
+                    TALER_MERCHANTDB_ReserveDetailsCallback cb,
+                    void *cb_cls);
 
 
   /**
-   * Obtain information about wire fees charged by an exchange,
-   * including signature (so we have proof).
+   * Delete private key of a reserve.
    *
    * @param cls closure
-   * @param exchange_pub public key of the exchange
-   * @param h_wire_method hash of wire method
-   * @param contract_date date of the contract to use for the lookup
-   * @param[out] wire_fee wire fee charged
-   * @param[out] closing_fee closing fee charged (irrelevant for us,
-   *              but needed to check signature)
-   * @param[out] start_date start of fee being used
-   * @param[out] end_date end of fee being used
-   * @param[out] exchange_sig signature of exchange over fee structure
-   * @return transaction status code
+   * @param instance_id instance to lookup payments for
+   * @param reserve_pub public key of the reserve to delete
+   * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*lookup_wire_fee)(void *cls,
-                     const struct TALER_MasterPublicKeyP *exchange_pub,
-                     const struct GNUNET_HashCode *h_wire_method,
-                     struct GNUNET_TIME_Absolute contract_date,
-                     struct TALER_Amount *wire_fee,
-                     struct TALER_Amount *closing_fee,
-                     struct GNUNET_TIME_Absolute *start_date,
-                     struct GNUNET_TIME_Absolute *end_date,
-                     struct TALER_MasterSignatureP *exchange_sig);
-
+  (*delete_reserve)(void *cls,
+                    const char *instance_id,
+                    const struct TALER_ReservePublicKeyP *reserve_pub);
 
   /**
-   * Function called when some backoffice staff decides to award or
-   * increase the refund on an existing contract.  This function
-   * MUST be called from within a transaction scope setup by the
-   * caller as it executes multiple SQL statements (NT).
+   * Purge all information about a reserve (including tips from it).
    *
    * @param cls closure
-   * @param merchant_pub merchant's instance public key
-   * @param h_contract_terms
-   * @param merchant_pub merchant's instance public key
-   * @param refund maximum refund to return to the customer for this contract
-   * @param reason 0-terminated UTF-8 string giving the reason why the customer
-   *               got a refund (free form, business-specific)
+   * @param instance_id instance to lookup payments for
+   * @param reserve_pub public key of the reserve to purge
    * @return transaction status
-   *        #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a refund is ABOVE the 
amount we
-   *        were originally paid and thus the transaction failed;
-   *        #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the request is valid,
-   *        regardless of whether it actually increased the refund beyond
-   *        what was already refunded (idempotency!)
    */
   enum GNUNET_DB_QueryStatus
-  (*increase_refund_for_contract_NT)(
-    void *cls,
-    const struct GNUNET_HashCode *h_contract_terms,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    const struct TALER_Amount *refund,
-    const char *reason);
+  (*purge_reserve)(void *cls,
+                   const char *instance_id,
+                   const struct TALER_ReservePublicKeyP *reserve_pub);
 
 
   /**
-   * Obtain refunds associated with a contract.
+   * Authorize a tip over @a amount from reserve @a reserve_pub.  Remember
+   * the authorization under @a tip_id for later, together with the
+   * @a justification.
    *
    * @param cls closure, typically a connection to the db
-   * @param merchant_pub public key of the merchant instance
-   * @param h_contract_terms hash code of the contract
-   * @param rc function to call for each coin on which there is a refund
-   * @param rc_cls closure for @a rc
-   * @return transaction status
+   * @param instance_id which instance should generate the tip
+   * @param reserve_pub which reserve is debited, NULL to pick one in the DB
+   * @param amount how high is the tip (with fees)
+   * @param justification why was the tip approved
+   * @param next_url where to send the URL post tip pickup
+   * @param[out] tip_id set to the unique ID for the tip
+   * @param[out] expiration set to when the tip expires
+   * @return transaction status,
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but 
has expired
+   *      #TALER_EC_TIP_AUTHORIZE_RESERVE_NOT_FOUND if the reserve is not known
+   *      #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has 
insufficient funds left
+   *      #TALER_EC_TIP_AUTHORIZE_DB_START_FAILURE on hard DB errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_LOOKUP_RESERVE_FAILURE on hard DB errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_UPDATE_RESERVE_FAILURE on hard DB errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_RESERVE_INVARIANT_FAILURE on hard DB 
errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_START_FAILURE on hard DB errors
+   *      #TALER_EC_TIP_AUTHORIZE_DB_SERIALIZATION_FAILURE on soft DB errors 
(client should retry)
+   *      #TALER_EC_NONE upon success
    */
-  enum GNUNET_DB_QueryStatus
-  (*get_refunds_from_contract_terms_hash)(
-    void *cls,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    const struct GNUNET_HashCode *h_contract_terms,
-    TALER_MERCHANTDB_RefundCallback rc,
-    void *rc_cls);
+  enum TALER_ErrorCode
+  (*authorize_tip)(void *cls,
+                   const char *instance_id,
+                   const struct TALER_ReservePublicKeyP *reserve_pub,
+                   const struct TALER_Amount *amount,
+                   const char *justification,
+                   const char *next_url,
+                   struct GNUNET_HashCode *tip_id,
+                   struct GNUNET_TIME_Absolute *expiration);
 
 
   /**
-   * Obtain refund proofs associated with a refund operation on a
-   * coin.
+   * Lookup pickup details for pickup @a pickup_id.
    *
    * @param cls closure, typically a connection to the db
-   * @param merchant_pub public key of the merchant instance
-   * @param h_contract_terms hash code of the contract
-   * @param coin_pub public key of the coin
-   * @param rtransaction_id identificator of the refund
-   * @param[out] exchange_pub public key of the exchange affirming the refund
-   * @param[out] exchange_sig signature of the exchange affirming the refund
+   * @param instance_id which instance should we lookup tip details for
+   * @param tip_id which tip should we lookup details on
+   * @param pickup_id which pickup should we lookup details on
+   * @param[out] exchange_url which exchange is the tip withdrawn from
+   * @param[out] reserve_priv private key the tip is withdrawn from (set if 
still available!)
+   * @param sigs_length length of the @a sigs array
+   * @param[out] sigs set to the (blind) signatures we have for this @a 
pickup_id,
+   *              those that are unavailable are left at NULL
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*get_refund_proof)(
-    void *cls,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    const struct GNUNET_HashCode *h_contract_terms,
-    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-    uint64_t rtransaction_id,
-    struct TALER_ExchangePublicKeyP *exchange_pub,
-    struct TALER_ExchangeSignatureP *exchange_sig);
+  (*lookup_pickup)(void *cls,
+                   const char *instance_id,
+                   const struct GNUNET_HashCode *tip_id,
+                   const struct GNUNET_HashCode *pickup_id,
+                   char **exchange_url,
+                   struct TALER_ReservePrivateKeyP *reserve_priv,
+                   unsigned int sigs_length,
+                   struct GNUNET_CRYPTO_RsaSignature *sigs[]);
 
 
   /**
-   * Store refund proofs associated with a refund operation on a
-   * coin.
+   * Lookup tip details for tip @a tip_id.
    *
    * @param cls closure, typically a connection to the db
-   * @param merchant_pub public key of the merchant instance
-   * @param h_contract_terms hash code of the contract
-   * @param coin_pub public key of the coin
-   * @param rtransaction_id identificator of the refund
-   * @param exchange_pub public key of the exchange affirming the refund
-   * @param exchange_sig signature of the exchange affirming the refund
+   * @param instance_id which instance should we lookup tip details for
+   * @param tip_id which tip should we lookup details on
+   * @param[out] total_authorized amount how high is the tip (with fees)
+   * @param[out] total_picked_up how much of the tip was so far picked up 
(with fees)
+   * @param[out] expiration set to when the tip expires
+   * @param[out] exchange_url set to the exchange URL where the reserve is
+   * @param[out] reserve_priv set to private key of reserve to be debited
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*put_refund_proof)(
-    void *cls,
-    const struct TALER_MerchantPublicKeyP *merchant_pub,
-    const struct GNUNET_HashCode *h_contract_terms,
-    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-    uint64_t rtransaction_id,
-    const struct TALER_ExchangePublicKeyP *exchange_pub,
-    const struct TALER_ExchangeSignatureP *exchange_sig);
+  (*lookup_tip)(void *cls,
+                const char *instance_id,
+                const struct GNUNET_HashCode *tip_id,
+                struct TALER_Amount *total_authorized,
+                struct TALER_Amount *total_picked_up,
+                struct GNUNET_TIME_Absolute *expiration,
+                char **exchange_url,
+                struct TALER_ReservePrivateKeyP *reserve_priv);
 
 
   /**
-   * 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.
+   * Lookup tips
    *
    * @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
+   * @param instance_id which instance should we lookup tips for
+   * @param expired should we include expired tips?
+   * @param limit maximum number of results to return, positive for
+   *   ascending row id, negative for descending
+   * @param offset row id to start returning results from
+   * @param cb function to call with tip data
+   * @param cb_cls closure for @a cb
+   * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*enable_tip_reserve_TR)(void *cls,
-                           const struct TALER_ReservePrivateKeyP *reserve_priv,
-                           const struct GNUNET_HashCode *credit_uuid,
-                           const struct TALER_Amount *credit,
-                           struct GNUNET_TIME_Absolute expiration);
+  (*lookup_tips)(void *cls,
+                 const char *instance_id,
+                 enum TALER_EXCHANGE_YesNoAll expired,
+                 int64_t limit,
+                 uint64_t offset,
+                 TALER_MERCHANTDB_TipsCallback cb,
+                 void *cb_cls);
 
 
   /**
-   * 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.
+   * Lookup tip details for tip @a tip_id.
    *
    * @param cls closure, typically a connection to the db
-   * @param justification why was the tip approved
-   * @param extra extra data that will be given to the customer's wallet
-   * @param amount how high is the tip (with fees)
-   * @param reserve_priv which reserve is debited
-   * @param exchange_url which exchange manages the tip
+   * @param instance_id which instance should we lookup tip details for
+   * @param tip_id which tip should we lookup details on
+   * @param fpu should we fetch details about individual pickups
+   * @param[out] total_authorized amount how high is the tip (with fees)
+   * @param[out] total_picked_up how much of the tip was so far picked up 
(with fees)
+   * @param[out] justification why was the tip approved
    * @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_TR)(void *cls,
-                      const char *justification,
-                      const json_t *extra,
-                      const struct TALER_Amount *amount,
-                      const struct TALER_ReservePrivateKeyP *reserve_priv,
-                      const char *exchange_url,
-                      struct GNUNET_TIME_Absolute *expiration,
-                      struct GNUNET_HashCode *tip_id);
-
-  /**
-   * Get the total amount of authorized tips for a tipping reserve.
-   *
-   * @param cls closure, typically a connection to the db
-   * @param reserve_priv which reserve to check
-   * @param[out] authorzed_amount amount we've authorized so far for tips
-   * @return transaction status, usually
-   *      #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT for success
-   *      #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the reserve_priv
-   *      does not identify a known tipping reserve
+   * @param[out] reserve_pub set to which reserve is debited
+   * @param[out] pickups_length set to the length of @e pickups
+   * @param[out] pickups if @a fpu is true, set to details about the pickup 
operations
+   * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*get_authorized_tip_amount)(void *cls,
-                               const struct
-                               TALER_ReservePrivateKeyP *reserve_priv,
-                               struct TALER_Amount *authorized_amount);
+  (*lookup_tip_details)(void *cls,
+                        const char *instance_id,
+                        const struct GNUNET_HashCode *tip_id,
+                        bool fpu,
+                        struct TALER_Amount *total_authorized,
+                        struct TALER_Amount *total_picked_up,
+                        char **justification,
+                        struct GNUNET_TIME_Absolute *expiration,
+                        struct TALER_ReservePublicKeyP *reserve_pub,
+                        unsigned int *pickups_length,
+                        struct TALER_MERCHANTDB_PickupDetails **pickups);
 
 
   /**
-   * Find out tip authorization details associated with @a tip_id
+   * Insert details about a tip pickup operation.  The @a total_picked_up
+   * UPDATES the total amount under the @a tip_id, while the @a
+   * total_requested is the amount to be associated with this @a pickup_id.
+   * While there is usually only one pickup event that picks up the entire
+   * amount, our schema allows for wallets to pick up the amount incrementally
+   * over multiple pick up operations.
    *
-   * @param cls closure, typically a connection to the d
+   * @param cls closure, typically a connection to the db
    * @param tip_id the unique ID for the tip
-   * @param[out] exchange_url set to the URL of the exchange (unless NULL)
-   * @param[out] extra extra data to pass to the wallet
-   * @param[out] amount set to the authorized amount (unless NULL)
-   * @param[out] amount_left set to the amount left (unless NULL)
-   * @param[out] timestamp set to the timestamp of the tip authorization 
(unless NULL)
+   * @param total_picked_up how much was picked up overall at this
+   *          point (includes @total_requested)
+   * @param pickup_id unique ID for the operation
+   * @param total_requested how much is being picked up in this operation
    * @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_url,
-                      json_t **extra,
-                      struct TALER_Amount *amount,
-                      struct TALER_Amount *amount_left,
-                      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_TR)(void *cls,
-                   const struct TALER_Amount *amount,
+  (*insert_pickup)(void *cls,
+                   const char *instance_id,
                    const struct GNUNET_HashCode *tip_id,
+                   const struct TALER_Amount *total_picked_up,
                    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
-   * @return #GNUNET_OK on success
-   */
-  void
-  (*rollback) (void *cls);
-
-
-  /**
-   * Start a transaction.
-   *
-   * @param cls the `struct PostgresClosure` with the plugin-specific state
-   * @param name unique name identifying the transaction (for debugging),
-   *             must point to a constant
-   * @return #GNUNET_OK on success
-   */
-  int
-  (*start) (void *cls,
-            const char *name);
-
-
-  /**
-   * Do a pre-flight check that we are not in an uncommitted transaction.
-   * If we are, try to commit the previous transaction and output a warning.
-   * Does not return anything, as we will continue regardless of the outcome.
-   *
-   * @param cls the `struct PostgresClosure` with the plugin-specific state
-   */
-  void
-  (*preflight) (void *cls);
+                   const struct TALER_Amount *total_requested);
 
 
   /**
-   * Commit the current transaction of a database connection.
+   * Insert blind signature obtained from the exchange during a
+   * tip pickup operation.
    *
-   * @param cls the `struct PostgresClosure` with the plugin-specific state
-   * @return transaction status code
+   * @param cls closure, typically a connection to the db
+   * @param pickup_id unique ID for the operation
+   * @param offset offset of the blind signature for the pickup
+   * @param blind_sig the blind signature
+   * @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
-  (*commit)(void *cls);
+  (*insert_pickup_blind_signature)(
+    void *cls,
+    const struct GNUNET_HashCode *pickup_id,
+    uint32_t offset,
+    const struct GNUNET_CRYPTO_RsaSignature *blind_sig);
 
 };
 
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 1561f57..d4b899a 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -7,34 +7,48 @@ if USE_COVERAGE
 endif
 
 lib_LTLIBRARIES = \
-  libtalermerchant.la \
-  libtalermerchanttesting.la
+  libtalermerchant.la
 
 libtalermerchant_la_LDFLAGS = \
   -version-info 2:0:0 \
   -no-undefined
 
-libtalermerchanttesting_la_LDFLAGS = \
-  -version-info 2:0:0 \
-  -no-undefined
-
 libtalermerchant_la_SOURCES = \
-  merchant_api_check_payment.c \
   merchant_api_common.c \
-  merchant_api_config.c \
-  merchant_api_history.c \
-  merchant_api_proposal.c \
-  merchant_api_proposal_lookup.c \
-  merchant_api_pay.c \
-  merchant_api_poll_payment.c \
-  merchant_api_refund.c \
-  merchant_api_refund_increase.c \
+  merchant_api_delete_instance.c \
+  merchant_api_delete_order.c \
+  merchant_api_delete_product.c \
+  merchant_api_delete_reserve.c \
+  merchant_api_get_config.c \
+  merchant_api_get_instance.c \
+  merchant_api_get_instances.c \
+  merchant_api_get_orders.c \
+  merchant_api_get_product.c \
+  merchant_api_get_products.c \
+  merchant_api_get_reserve.c \
+  merchant_api_get_reserves.c \
+  merchant_api_get_transfers.c \
+  merchant_api_lock_product.c \
+  merchant_api_merchant_get_order.c \
+  merchant_api_merchant_get_tip.c \
+  merchant_api_patch_instance.c \
+  merchant_api_patch_product.c \
+  merchant_api_post_instances.c \
+  merchant_api_post_orders.c \
+  merchant_api_post_order_abort.c \
+  merchant_api_post_order_claim.c \
+  merchant_api_post_order_pay.c \
+  merchant_api_post_order_refund.c \
+  merchant_api_post_products.c \
+  merchant_api_post_reserves.c \
+  merchant_api_post_transfers.c \
+  merchant_api_get_tips.c \
   merchant_api_tip_authorize.c \
   merchant_api_tip_pickup.c \
   merchant_api_tip_pickup2.c \
-  merchant_api_tip_query.c \
-  merchant_api_track_transaction.c \
-  merchant_api_track_transfer.c
+  merchant_api_wallet_get_tip.c \
+  merchant_api_wallet_get_order.c
+
 libtalermerchant_la_LIBADD = \
   -ltalerexchange \
   -ltalercurl \
@@ -46,43 +60,6 @@ libtalermerchant_la_LIBADD = \
   -ljansson \
   $(XLIB)
 
-libtalermerchanttesting_la_SOURCES = \
-  testing_api_cmd_check_payment.c \
-  testing_api_cmd_config.c \
-  testing_api_cmd_history.c \
-  testing_api_cmd_pay.c \
-  testing_api_cmd_pay_abort.c \
-  testing_api_cmd_pay_abort_refund.c \
-  testing_api_cmd_poll_payment.c \
-  testing_api_cmd_proposal.c \
-  testing_api_cmd_proposal_lookup.c \
-  testing_api_cmd_refund_increase.c \
-  testing_api_cmd_refund_lookup.c \
-  testing_api_cmd_rewind.c \
-  testing_api_cmd_tip_authorize.c \
-  testing_api_cmd_tip_pickup.c \
-  testing_api_cmd_tip_query.c \
-  testing_api_cmd_track_transaction.c \
-  testing_api_cmd_track_transfer.c \
-  testing_api_helpers.c \
-  testing_api_trait_merchant_sig.c \
-  testing_api_trait_string.c \
-  testing_api_trait_hash.c \
-  testing_api_trait_planchet.c \
-  testing_api_trait_refund_entry.c
-
-libtalermerchanttesting_la_LIBADD = \
-  libtalermerchant.la \
-  -ltalerexchange \
-  -ltalerjson \
-  -ltalerutil \
-  -lgnunetcurl \
-  -lgnunetjson \
-  -lgnunetutil \
-  -ljansson \
-  -ltalertesting \
-  $(XLIB)
-
 if HAVE_LIBCURL
 libtalermerchant_la_LIBADD += -lcurl
 else
@@ -90,63 +67,3 @@ if HAVE_LIBGNURL
 libtalermerchant_la_LIBADD += -lgnurl
 endif
 endif
-
-if HAVE_TALERFAKEBANK
-check_PROGRAMS = \
-  test_merchant_api
-
-if HAVE_TWISTER
-check_PROGRAMS += test_merchant_api_twisted
-endif
-
-endif
-
-TESTS = \
-  $(check_PROGRAMS)
-
-test_merchant_api_twisted_SOURCES = \
-  test_merchant_api_twisted.c
-test_merchant_api_twisted_LDADD = \
-  $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
-  libtalermerchant.la \
-  $(LIBGCRYPT_LIBS) \
-  -ltalertesting \
-  -ltalermerchanttesting \
-  -ltalertwistertesting \
-  -ltalerfakebank \
-  -ltalerbank \
-  -ltalerexchange \
-  -ltalerjson \
-  -ltalerutil \
-  -lgnunetjson \
-  -lgnunetcurl \
-  -lgnunetutil \
-  -ljansson \
-  -ltalertwister
-
-test_merchant_api_SOURCES = \
-  test_merchant_api.c
-test_merchant_api_LDADD = \
-  $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
-  libtalermerchant.la \
-  $(LIBGCRYPT_LIBS) \
-  -ltalertesting \
-  -ltalermerchanttesting \
-  -ltalerfakebank \
-  -ltalerbank \
-  -ltalerexchange \
-  -ltalerjson \
-  -ltalerutil \
-  -lgnunetjson \
-  -lgnunetcurl \
-  -lgnunetutil \
-  -ljansson
-
-EXTRA_DIST = \
-  test_merchant_api.conf \
-  test_merchant_api_twisted.conf \
-  test_merchant_api_proxy_merchant.conf \
-  test_merchant_api_proxy_exchange.conf \
-  test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv \
-  test_merchant_api_home/.config/taler/exchange/account-2.json \
-  test_merchant.priv
diff --git a/src/lib/merchant_api_check_payment.c 
b/src/lib/merchant_api_check_payment.c
deleted file mode 100644
index 5196d5b..0000000
--- a/src/lib/merchant_api_check_payment.c
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2018, 2020 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_check_payment.c
- * @brief Implementation of the /check-payment GET request
- * @author Christian Grothoff
- * @author Marcello Stanisci
- * @author Florian Dold
- */
-#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>
-
-
-/**
- * @brief A check payment operation handle
- */
-struct TALER_MERCHANT_CheckPaymentOperation
-{
-
-  /**
-   * The url for this request.
-   */
-  char *url;
-
-  /**
-   * Handle for the request.
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the result.
-   */
-  TALER_MERCHANT_CheckPaymentCallback 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 GET /check-payment request.
- *
- * @param cls the `struct TALER_MERCHANT_CheckPaymentOperation`
- * @param response_code HTTP response code, 0 on error
- * @param json response body, should be NULL
- */
-static void
-handle_check_payment_finished (void *cls,
-                               long response_code,
-                               const void *response)
-{
-  struct TALER_MERCHANT_CheckPaymentOperation *cpo = cls;
-  struct TALER_Amount refund_amount = { 0 };
-  const json_t *json = response;
-  const json_t *refunded;
-
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount ("refund_amount",
-                            &refund_amount),
-    GNUNET_JSON_spec_end ()
-  };
-
-  cpo->job = NULL;
-
-  if (MHD_HTTP_OK != response_code)
-  {
-    struct TALER_MERCHANT_HttpResponse hr;
-
-    TALER_MERCHANT_parse_error_details_ (response,
-                                         response_code,
-                                         &hr);
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Checking payment failed with HTTP status code %u/%d\n",
-                (unsigned int) response_code,
-                (int) hr.ec);
-    GNUNET_break_op (0);
-    cpo->cb (cpo->cb_cls,
-             &hr,
-             GNUNET_SYSERR,
-             GNUNET_SYSERR,
-             NULL,
-             NULL);
-    TALER_MERCHANT_check_payment_cancel (cpo);
-    return;
-  }
-
-  if (! json_boolean_value (json_object_get (json, "paid")))
-  {
-    const char *taler_pay_uri = json_string_value (json_object_get (json,
-                                                                    
"taler_pay_uri"));
-    if (NULL == taler_pay_uri)
-    {
-      struct TALER_MERCHANT_HttpResponse hr = {
-        .ec = TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
-        .reply = json
-      };
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "no taler_pay_uri in unpaid check-payment response\n");
-      GNUNET_break_op (0);
-      cpo->cb (cpo->cb_cls,
-               &hr,
-               GNUNET_SYSERR,
-               GNUNET_SYSERR,
-               NULL,
-               NULL);
-    }
-    else
-    {
-      struct TALER_MERCHANT_HttpResponse hr = {
-        .reply = json,
-        .http_status = MHD_HTTP_OK
-      };
-      cpo->cb (cpo->cb_cls,
-               &hr,
-               GNUNET_NO,
-               GNUNET_NO,
-               NULL,
-               taler_pay_uri);
-    }
-    TALER_MERCHANT_check_payment_cancel (cpo);
-    return;
-  }
-
-  if ( (NULL == (refunded = json_object_get (json, "refunded"))) ||
-       ( (json_true () == refunded) &&
-         (GNUNET_OK !=
-          GNUNET_JSON_parse (json,
-                             spec,
-                             NULL, NULL)) ) )
-  {
-    struct TALER_MERCHANT_HttpResponse hr = {
-      .ec = TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
-      .reply = json
-    };
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "check payment failed to parse JSON\n");
-    GNUNET_break_op (0);
-    cpo->cb (cpo->cb_cls,
-             &hr,
-             GNUNET_SYSERR,
-             GNUNET_SYSERR,
-             NULL,
-             NULL);
-    TALER_MERCHANT_check_payment_cancel (cpo);
-    return;
-  }
-  {
-    struct TALER_MERCHANT_HttpResponse hr = {
-      .reply = json,
-      .http_status = MHD_HTTP_OK
-    };
-
-    cpo->cb (cpo->cb_cls,
-             &hr,
-             GNUNET_YES,
-             (json_true () == refunded),
-             (json_true () == refunded) ? &refund_amount : NULL,
-             NULL);
-  }
-  TALER_MERCHANT_check_payment_cancel (cpo);
-}
-
-
-/**
- * Issue a /check-payment request to the backend.  Checks the status
- * of a payment.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param order_id order id to identify the payment
- * @param session_id sesion id for the payment (or NULL if the payment is not 
bound to a session)
- * @param timeout timeout to use in long polling (how long may the server wait 
to reply
- *        before generating an unpaid response). Note that this is just 
provided to
- *        the server, we as client will block until the response comes back or 
until
- *        #TALER_MERCHANT_poll_payment_cancel() is called.
- * @param check_payment_cb callback which will work the response gotten from 
the backend
- * @param check_payment_cb_cls closure to pass to @a check_payment_cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_CheckPaymentOperation *
-TALER_MERCHANT_check_payment (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  const char *order_id,
-  const char *session_id,
-  struct GNUNET_TIME_Relative timeout,
-  TALER_MERCHANT_CheckPaymentCallback check_payment_cb,
-  void *check_payment_cb_cls)
-{
-  struct TALER_MERCHANT_CheckPaymentOperation *cpo;
-  CURL *eh;
-  char *timeout_s;
-  unsigned int ts;
-  long tlong;
-
-  GNUNET_assert (NULL != backend_url);
-  GNUNET_assert (NULL != order_id);
-  ts = (unsigned int) (timeout.rel_value_us
-                       / GNUNET_TIME_UNIT_SECONDS.rel_value_us);
-  /* set curl timeout to *our* long poll timeout plus one minute
-     (for network latency and processing delays) */
-  tlong = (long) (GNUNET_TIME_relative_add (timeout,
-                                            GNUNET_TIME_UNIT_MINUTES).
-                  rel_value_us
-                  / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
-  GNUNET_asprintf (&timeout_s,
-                   "%u",
-                   ts);
-  cpo = GNUNET_new (struct TALER_MERCHANT_CheckPaymentOperation);
-  cpo->ctx = ctx;
-  cpo->cb = check_payment_cb;
-  cpo->cb_cls = check_payment_cb_cls;
-  cpo->url = TALER_url_join (backend_url, "check-payment",
-                             "order_id", order_id,
-                             "session_id", session_id,
-                             (0 != ts) ? "timeout" : NULL,
-                             timeout_s,
-                             NULL);
-  if (NULL == cpo->url)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not construct request URL.\n");
-    GNUNET_free (cpo);
-    return NULL;
-  }
-  GNUNET_free (timeout_s);
-  eh = curl_easy_init ();
-  if (CURLE_OK != curl_easy_setopt (eh,
-                                    CURLOPT_URL,
-                                    cpo->url))
-  {
-    GNUNET_break (0);
-    curl_easy_cleanup (eh);
-    GNUNET_free (cpo->url);
-    GNUNET_free (cpo);
-    return NULL;
-  }
-  if (CURLE_OK != curl_easy_setopt (eh,
-                                    CURLOPT_TIMEOUT_MS,
-                                    tlong))
-  {
-    GNUNET_break (0);
-    curl_easy_cleanup (eh);
-    GNUNET_free (cpo->url);
-    GNUNET_free (cpo);
-    return NULL;
-  }
-
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Checking payment from %s\n",
-              cpo->url);
-  if (NULL == (cpo->job = GNUNET_CURL_job_add (ctx,
-                                               eh,
-                                               GNUNET_YES,
-                                               &handle_check_payment_finished,
-                                               cpo)))
-  {
-    GNUNET_break (0);
-    GNUNET_free (cpo->url);
-    GNUNET_free (cpo);
-    return NULL;
-  }
-  return cpo;
-}
-
-
-/**
- * Cancel a GET /check-payment request.
- *
- * @param cph handle to the request to be canceled
- */
-void
-TALER_MERCHANT_check_payment_cancel (
-  struct TALER_MERCHANT_CheckPaymentOperation *cph)
-{
-  if (NULL != cph->job)
-  {
-    GNUNET_CURL_job_cancel (cph->job);
-    cph->job = NULL;
-  }
-  GNUNET_free (cph->url);
-  GNUNET_free (cph);
-}
-
-
-/* end of merchant_api_check_payment.c */
diff --git a/src/lib/merchant_api_common.c b/src/lib/merchant_api_common.c
index cb8de65..4d69e72 100644
--- a/src/lib/merchant_api_common.c
+++ b/src/lib/merchant_api_common.c
@@ -116,3 +116,37 @@ TALER_MERCHANT_parse_error_details_ (const json_t 
*response,
     hr->exchange_hint = json_string_value (jc);
   }
 }
+
+
+/**
+ * Construct a new base URL using the existing @a base_url
+ * and the given @a instance_id.  The result WILL end with
+ * '/'.
+ *
+ * @param base_url a merchant base URL without "/instances/" in it,
+ *         must not be the empty string; MAY end with '/'.
+ * @param instance_id ID of an instance
+ * @return "${base_url}/instances/${instance_id}/"
+ */
+char *
+TALER_MERCHANT_baseurl_add_instance (const char *base_url,
+                                     const char *instance_id)
+{
+  char *ret;
+  bool end_sl;
+
+  if ('\0' == *base_url)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  end_sl = '/' == base_url[strlen (base_url) - 1];
+
+  GNUNET_asprintf (&ret,
+                   (end_sl)
+                   ? "%sinstances/%s/"
+                   : "%s/instances/%s/",
+                   base_url,
+                   instance_id);
+  return ret;
+}
diff --git a/src/lib/merchant_api_delete_instance.c 
b/src/lib/merchant_api_delete_instance.c
new file mode 100644
index 0000000..edf73f8
--- /dev/null
+++ b/src/lib/merchant_api_delete_instance.c
@@ -0,0 +1,259 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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_delete_instance.c
+ * @brief Implementation of the DELETE /instance/$ID request of the merchant's 
HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a DELETE /instances/$ID operation.
+ */
+struct TALER_MERCHANT_InstanceDeleteHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_InstanceDeleteCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /instances/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_InstanceDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_delete_instance_finished (void *cls,
+                                 long response_code,
+                                 const void *response)
+{
+  struct TALER_MERCHANT_InstanceDeleteHandle *idh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  idh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /instances/$ID response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  idh->cb (idh->cb_cls,
+           &hr);
+  TALER_MERCHANT_instance_delete_cancel (idh);
+}
+
+
+/**
+ * Delete the private key of an instance of a backend, thereby disabling the
+ * instance for future requests.  Will preserve the other instance data
+ * (i.e. for taxation).
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id which instance should be deleted
+ * @param purge purge instead of just deleting
+ * @param instances_cb function to call with the
+ *        backend's return
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+static struct TALER_MERCHANT_InstanceDeleteHandle *
+instance_delete (struct GNUNET_CURL_Context *ctx,
+                 const char *backend_url,
+                 const char *instance_id,
+                 bool purge,
+                 TALER_MERCHANT_InstanceDeleteCallback cb,
+                 void *cb_cls)
+{
+  struct TALER_MERCHANT_InstanceDeleteHandle *idh;
+
+  idh = GNUNET_new (struct TALER_MERCHANT_InstanceDeleteHandle);
+  idh->ctx = ctx;
+  idh->cb = cb;
+  idh->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/instances/%s",
+                     instance_id);
+    if (purge)
+      idh->url = TALER_url_join (backend_url,
+                                 path,
+                                 "purge",
+                                 "yes",
+                                 NULL);
+    else
+      idh->url = TALER_url_join (backend_url,
+                                 path,
+                                 NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == idh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (idh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              idh->url);
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_URL,
+                                     idh->url));
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_CUSTOMREQUEST,
+                                     MHD_HTTP_METHOD_DELETE));
+    idh->job = GNUNET_CURL_job_add (ctx,
+                                    eh,
+                                    GNUNET_YES,
+                                    &handle_delete_instance_finished,
+                                    idh);
+  }
+  return idh;
+}
+
+
+/**
+ * Delete the private key of an instance of a backend, thereby disabling the
+ * instance for future requests.  Will preserve the other instance data
+ * (i.e. for taxation).
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id which instance should be deleted
+ * @param instances_cb function to call with the
+ *        backend's return
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstanceDeleteHandle *
+TALER_MERCHANT_instance_delete (struct GNUNET_CURL_Context *ctx,
+                                const char *backend_url,
+                                const char *instance_id,
+                                TALER_MERCHANT_InstanceDeleteCallback cb,
+                                void *cb_cls)
+{
+  return instance_delete (ctx,
+                          backend_url,
+                          instance_id,
+                          false,
+                          cb,
+                          cb_cls);
+}
+
+
+/**
+ * Purge all data associated with an instance. Use with
+ * extreme caution.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id which instance should be deleted
+ * @param instances_cb function to call with the
+ *        backend's return
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstanceDeleteHandle *
+TALER_MERCHANT_instance_purge (struct GNUNET_CURL_Context *ctx,
+                               const char *backend_url,
+                               const char *instance_id,
+                               TALER_MERCHANT_InstanceDeleteCallback cb,
+                               void *cb_cls)
+{
+  return instance_delete (ctx,
+                          backend_url,
+                          instance_id,
+                          true,
+                          cb,
+                          cb_cls);
+}
+
+
+/**
+ * Cancel DELETE /instance/$ID request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param idh request to cancel.
+ */
+void
+TALER_MERCHANT_instance_delete_cancel (
+  struct TALER_MERCHANT_InstanceDeleteHandle *idh)
+{
+  if (NULL != idh->job)
+    GNUNET_CURL_job_cancel (idh->job);
+  GNUNET_free (idh->url);
+  GNUNET_free (idh);
+}
diff --git a/src/lib/merchant_api_delete_order.c 
b/src/lib/merchant_api_delete_order.c
new file mode 100644
index 0000000..4b08e8c
--- /dev/null
+++ b/src/lib/merchant_api_delete_order.c
@@ -0,0 +1,188 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_delete_order.c
+ * @brief Implementation of the DELETE /orders/$ORDER_ID request of the 
merchant's HTTP API
+ * @author Jonathan Buchanan
+ */
+#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>
+
+/**
+ * Handle for a DELETE /orders/$ID operation.
+ */
+struct TALER_MERCHANT_OrderDeleteHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_OrderDeleteCallback 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 DELETE /orders/$ORDER_ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OrderDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_delete_order_finished (void *cls,
+                              long response_code,
+                              const void *response)
+{
+  struct TALER_MERCHANT_OrderDeleteHandle *odh = cls;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = NULL,
+  };
+
+  odh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /orders/$ID response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    break;
+  case MHD_HTTP_CONFLICT:
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u\n",
+                (unsigned int) response_code);
+    break;
+  }
+  odh->cb (odh->cb_cls,
+           &hr);
+  TALER_MERCHANT_order_delete_cancel (odh);
+}
+
+
+/**
+ * Make a DELETE /orders/$ID request to delete a order from our
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param order_id identifier of the order
+ * @param cb function to call with the backend's deletion status
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_OrderDeleteHandle *
+TALER_MERCHANT_order_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *order_id,
+  TALER_MERCHANT_OrderDeleteCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_OrderDeleteHandle *odh;
+
+  odh = GNUNET_new (struct TALER_MERCHANT_OrderDeleteHandle);
+  odh->ctx = ctx;
+  odh->cb = cb;
+  odh->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/orders/%s",
+                     order_id);
+
+    odh->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == odh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request url.\n");
+    GNUNET_free (odh);
+    return NULL;
+  }
+
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_URL,
+                                     odh->url));
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_CUSTOMREQUEST,
+                                     MHD_HTTP_METHOD_DELETE));
+    odh->job = GNUNET_CURL_job_add (ctx,
+                                    eh,
+                                    GNUNET_YES,
+                                    &handle_delete_order_finished,
+                                    odh);
+  }
+  return odh;
+}
+
+
+/**
+ * Cancel DELETE /orders/$ID operation.
+ *
+ * @param odh operation to cancel
+ */
+void
+TALER_MERCHANT_order_delete_cancel (
+  struct TALER_MERCHANT_OrderDeleteHandle *odh)
+{
+  if (NULL != odh->job)
+    GNUNET_CURL_job_cancel (odh->job);
+  GNUNET_free (odh->url);
+  GNUNET_free (odh);
+}
diff --git a/src/lib/merchant_api_delete_product.c 
b/src/lib/merchant_api_delete_product.c
new file mode 100644
index 0000000..88607b4
--- /dev/null
+++ b/src/lib/merchant_api_delete_product.c
@@ -0,0 +1,200 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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_delete_product.c
+ * @brief Implementation of the DELETE /product/$ID request of the merchant's 
HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a DELETE /products/$ID operation.
+ */
+struct TALER_MERCHANT_ProductDeleteHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ProductDeleteCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /products/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ProductDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_delete_product_finished (void *cls,
+                                long response_code,
+                                const void *response)
+{
+  struct TALER_MERCHANT_ProductDeleteHandle *pdh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  pdh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /products/$ID response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_CONFLICT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  pdh->cb (pdh->cb_cls,
+           &hr);
+  TALER_MERCHANT_product_delete_cancel (pdh);
+}
+
+
+/**
+ * Make a DELETE /products/$ID request to delete a product from our
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier of the product
+ * @param cb function to call with the backend's deletion status
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductDeleteHandle *
+TALER_MERCHANT_product_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  TALER_MERCHANT_ProductDeleteCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_ProductDeleteHandle *pdh;
+
+  pdh = GNUNET_new (struct TALER_MERCHANT_ProductDeleteHandle);
+  pdh->ctx = ctx;
+  pdh->cb = cb;
+  pdh->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/products/%s",
+                     product_id);
+    pdh->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == pdh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (pdh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              pdh->url);
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_URL,
+                                     pdh->url));
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_CUSTOMREQUEST,
+                                     MHD_HTTP_METHOD_DELETE));
+    pdh->job = GNUNET_CURL_job_add (ctx,
+                                    eh,
+                                    GNUNET_YES,
+                                    &handle_delete_product_finished,
+                                    pdh);
+  }
+  return pdh;
+}
+
+
+/**
+ * Cancel DELETE /product/$ID request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param pdh request to cancel.
+ */
+void
+TALER_MERCHANT_product_delete_cancel (
+  struct TALER_MERCHANT_ProductDeleteHandle *pdh)
+{
+  if (NULL != pdh->job)
+    GNUNET_CURL_job_cancel (pdh->job);
+  GNUNET_free (pdh->url);
+  GNUNET_free (pdh);
+}
diff --git a/src/lib/merchant_api_delete_reserve.c 
b/src/lib/merchant_api_delete_reserve.c
new file mode 100644
index 0000000..e765201
--- /dev/null
+++ b/src/lib/merchant_api_delete_reserve.c
@@ -0,0 +1,262 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_delete_reserve.c
+ * @brief Implementation of the DELETE /reserves/$RESERVE_PUB request of the 
merchant's HTTP API
+ * @author Jonathan Buchanan
+ */
+ #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>
+
+
+/**
+ * Handle for a DELETE /reserves/$RESERVE_PUB operation.
+ */
+struct TALER_MERCHANT_ReserveDeleteHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ReserveDeleteCallback 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 DELETE /reserves/$RESERVE_PUB request.
+ *
+ * @param cls the `struct TALER_MERCHANT_InstanceDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_delete_reserve_finished (void *cls,
+                                long response_code,
+                                const void *response)
+{
+  struct TALER_MERCHANT_ReserveDeleteHandle *rdh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  rdh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /reserves/$ID response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  rdh->cb (rdh->cb_cls,
+           &hr);
+  TALER_MERCHANT_reserve_delete_cancel (rdh);
+}
+
+
+/**
+ * Delete the private key of a reserve.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id which instance should be deleted
+ * @param purge purge instead of just deleting
+ * @param instances_cb function to call with the
+ *        backend's return
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+static struct TALER_MERCHANT_ReserveDeleteHandle *
+reserve_delete (struct GNUNET_CURL_Context *ctx,
+                const char *backend_url,
+                const struct TALER_ReservePublicKeyP *reserve_pub,
+                bool purge,
+                TALER_MERCHANT_ReserveDeleteCallback cb,
+                void *cb_cls)
+{
+  struct TALER_MERCHANT_ReserveDeleteHandle *rdh;
+
+  rdh = GNUNET_new (struct TALER_MERCHANT_ReserveDeleteHandle);
+  rdh->ctx = ctx;
+  rdh->cb = cb;
+  rdh->cb_cls = cb_cls;
+  {
+    char res_str[sizeof (*reserve_pub) * 2];
+    char arg_str[sizeof (res_str) + 32];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (reserve_pub,
+                                         sizeof (*reserve_pub),
+                                         res_str,
+                                         sizeof (res_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "private/reserves/%s",
+                     res_str);
+    if (purge)
+      rdh->url = TALER_url_join (backend_url,
+                                 arg_str,
+                                 "purge",
+                                 "yes",
+                                 NULL);
+    else
+      rdh->url = TALER_url_join (backend_url,
+                                 arg_str,
+                                 NULL);
+  }
+  if (NULL == rdh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (rdh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              rdh->url);
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_URL,
+                                     rdh->url));
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_CUSTOMREQUEST,
+                                     MHD_HTTP_METHOD_DELETE));
+    rdh->job = GNUNET_CURL_job_add (ctx,
+                                    eh,
+                                    GNUNET_YES,
+                                    &handle_delete_reserve_finished,
+                                    rdh);
+  }
+  return rdh;
+}
+
+
+/**
+ * Issue a DELETE /reserve/$RESERVE_ID request to the backend.  Only
+ * deletes the private key of the reserve, preserves tipping data.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param reserve_pub which reserve should be queried
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_ReserveDeleteHandle *
+TALER_MERCHANT_reserve_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  TALER_MERCHANT_ReserveDeleteCallback cb,
+  void *cb_cls)
+{
+  return reserve_delete (ctx,
+                         backend_url,
+                         reserve_pub,
+                         false,
+                         cb,
+                         cb_cls);
+}
+
+
+/**
+ * Issue a DELETE /reserve/$RESERVE_ID request to the backend.
+ * Purges the reserve, deleting all associated data. DANGEROUS.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param reserve_pub which reserve should be queried
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_ReserveDeleteHandle *
+TALER_MERCHANT_reserve_purge (struct GNUNET_CURL_Context *ctx,
+                              const char *backend_url,
+                              const struct TALER_ReservePublicKeyP 
*reserve_pub,
+                              TALER_MERCHANT_ReserveDeleteCallback cb,
+                              void *cb_cls)
+{
+  return reserve_delete (ctx,
+                         backend_url,
+                         reserve_pub,
+                         true,
+                         cb,
+                         cb_cls);
+}
+
+
+/**
+ * Cancel a DELETE (or purge) /reserve/$RESERVE_ID request.
+ *
+ * @param rdh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_reserve_delete_cancel (
+  struct TALER_MERCHANT_ReserveDeleteHandle *rdh)
+{
+  if (NULL != rdh->job)
+    GNUNET_CURL_job_cancel (rdh->job);
+  GNUNET_free (rdh->url);
+  GNUNET_free (rdh);
+}
diff --git a/src/lib/merchant_api_config.c b/src/lib/merchant_api_get_config.c
similarity index 73%
rename from src/lib/merchant_api_config.c
rename to src/lib/merchant_api_get_config.c
index 1d958a8..8a2d88b 100644
--- a/src/lib/merchant_api_config.c
+++ b/src/lib/merchant_api_get_config.c
@@ -15,7 +15,7 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/merchant_api_config.c
+ * @file lib/merchant_api_get_config.c
  * @brief Implementation of the /config request of the merchant's HTTP API
  * @author Christian Grothoff
  */
@@ -74,71 +74,9 @@ struct TALER_MERCHANT_ConfigGetHandle
 };
 
 
-/**
- * Parse instance information from @a ia.
- *
- * @param ia JSON array (or NULL!) with instance data
- * @param[in,out] ci config information to update
- * @return #GNUNET_OK on success
- */
-static int
-parse_instances (const json_t *ia,
-                 struct TALER_MERCHANT_ConfigInformation *ci)
-{
-  size_t index;
-  json_t *value;
-  int ret;
-
-  if (NULL == ia)
-    return GNUNET_OK; /* permit not disclosing instances for now */
-  if (! json_is_array (ia))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_array_grow (ci->iis,
-                     ci->iis_len,
-                     json_array_size (ia));
-  ret = GNUNET_OK;
-  json_array_foreach (ia, index, value) {
-    struct TALER_MERCHANT_InstanceInformation *ii = &ci->iis[index];
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_fixed_auto ("merchant_pub",
-                                   &ii->merchant_pub),
-      GNUNET_JSON_spec_string ("instance_baseurl",
-                               &ii->instance_baseurl),
-      GNUNET_JSON_spec_string ("name",
-                               &ii->name),
-      GNUNET_JSON_spec_end ()
-    };
-    json_t *teb;
-
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (value,
-                           spec,
-                           NULL, NULL))
-    {
-      GNUNET_break_op (0);
-      ret = GNUNET_SYSERR;
-      continue;
-    }
-    teb = json_object_get (value,
-                           "tipping_exchange_baseurl");
-    if (! json_is_string (teb))
-    {
-      GNUNET_break_op (0);
-      ret = GNUNET_SYSERR;
-      continue;
-    }
-    ii->tipping_exchange_baseurl = json_string_value (teb);
-  }
-  return ret;
-}
-
-
 /**
  * Function called when we're done processing the
- * HTTP /track/transaction request.
+ * HTTP /config request.
  *
  * @param cls the `struct TALER_MERCHANT_ConfigGetHandle`
  * @param response_code HTTP response code, 0 on error
@@ -165,10 +103,7 @@ handle_config_finished (void *cls,
   {
   case MHD_HTTP_OK:
     {
-      struct TALER_MERCHANT_ConfigInformation vi = {
-        .iis = NULL,
-        .iis_len = 0
-      };
+      struct TALER_MERCHANT_ConfigInformation vi;
       enum TALER_MERCHANT_VersionCompatibility vc =
         TALER_MERCHANT_VC_PROTOCOL_ERROR;
       struct GNUNET_JSON_Specification spec[] = {
@@ -219,25 +154,10 @@ handle_config_finished (void *cls,
           }
         }
       }
-      if (0 == (vc & (TALER_MERCHANT_VC_INCOMPATIBLE
-                      | TALER_MERCHANT_VC_PROTOCOL_ERROR)))
-      {
-        if (GNUNET_OK !=
-            parse_instances (json_object_get (json,
-                                              "instances"),
-                             &vi))
-        {
-          /* Let's keep the 200 OK, as we got at least the version data */
-          hr.ec = TALER_EC_INVALID_RESPONSE;
-        }
-      }
       vgh->cb (vgh->cb_cls,
                &hr,
                &vi,
                vc);
-      GNUNET_array_grow (vi.iis,
-                         vi.iis_len,
-                         0);
       TALER_MERCHANT_config_get_cancel (vgh);
       return;
     }
@@ -294,11 +214,9 @@ TALER_MERCHANT_config_get (struct GNUNET_CURL_Context *ctx,
     GNUNET_free (vgh);
     return NULL;
   }
-
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Requesting URL '%s'\n",
               vgh->url);
-
   eh = curl_easy_init ();
   GNUNET_assert (CURLE_OK ==
                  curl_easy_setopt (eh,
@@ -333,4 +251,4 @@ TALER_MERCHANT_config_get_cancel (struct 
TALER_MERCHANT_ConfigGetHandle *vgh)
 }
 
 
-/* end of merchant_api_config.c */
+/* end of merchant_api_config_get.c */
diff --git a/src/lib/merchant_api_get_instance.c 
b/src/lib/merchant_api_get_instance.c
new file mode 100644
index 0000000..0007fb7
--- /dev/null
+++ b/src/lib/merchant_api_get_instance.c
@@ -0,0 +1,293 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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_get_instance.c
+ * @brief Implementation of the GET /instance/$ID request of the merchant's 
HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /instances/$ID operation.
+ */
+struct TALER_MERCHANT_InstanceGetHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_InstanceGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /instances/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_InstanceGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_get_instance_finished (void *cls,
+                              long response_code,
+                              const void *response)
+{
+  struct TALER_MERCHANT_InstanceGetHandle *igh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  igh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /instances/$ID response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    {
+      json_t *accounts;
+      const char *name;
+      struct TALER_MerchantPublicKeyP merchant_pub;
+      json_t *address;
+      json_t *jurisdiction;
+      struct TALER_Amount default_max_wire_fee;
+      uint32_t default_wire_fee_amortization;
+      struct TALER_Amount default_max_deposit_fee;
+      struct GNUNET_TIME_Relative default_wire_transfer_delay;
+      struct GNUNET_TIME_Relative default_pay_delay;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("accounts",
+                               &accounts),
+        GNUNET_JSON_spec_string ("name",
+                                 &name),
+        GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                     &merchant_pub),
+        GNUNET_JSON_spec_json ("address",
+                               &address),
+        GNUNET_JSON_spec_json ("jurisdiction",
+                               &jurisdiction),
+        TALER_JSON_spec_amount ("default_max_wire_fee",
+                                &default_max_wire_fee),
+        GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
+                                 &default_wire_fee_amortization),
+        TALER_JSON_spec_amount ("default_max_deposit_fee",
+                                &default_max_deposit_fee),
+        GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
+                                        &default_wire_transfer_delay),
+        GNUNET_JSON_spec_relative_time ("default_pay_delay",
+                                        &default_pay_delay),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if ( (GNUNET_OK ==
+            GNUNET_JSON_parse (json,
+                               spec,
+                               NULL, NULL)) &&
+           (json_is_array (accounts)) )
+      {
+        unsigned int accounts_length = json_array_size (accounts);
+        struct TALER_MERCHANT_Account aa[accounts_length];
+        const char *payto_uris[accounts_length];
+        size_t index;
+        json_t *value;
+        int ret = GNUNET_OK;
+
+        memset (payto_uris, 0, sizeof (payto_uris));
+        json_array_foreach (accounts, index, value)
+        {
+          struct GNUNET_JSON_Specification spec[] = {
+            GNUNET_JSON_spec_fixed_auto ("salt",
+                                         &aa[index].salt),
+            GNUNET_JSON_spec_string ("payto_uri",
+                                     &payto_uris[index]),
+            GNUNET_JSON_spec_fixed_auto ("h_wire",
+                                         &aa[index].h_wire),
+            GNUNET_JSON_spec_bool ("active",
+                                   &aa[index].active),
+            GNUNET_JSON_spec_end ()
+          };
+
+          if (GNUNET_OK !=
+              GNUNET_JSON_parse (value,
+                                 spec,
+                                 NULL, NULL))
+          {
+            GNUNET_break_op (0);
+            ret = GNUNET_SYSERR;
+            break;
+          }
+          aa[index].payto_uri = payto_uris[index];
+        }
+
+        if (GNUNET_OK == ret)
+        {
+          struct TALER_MERCHANT_InstanceDetails details = {
+            .name = name,
+            .merchant_pub = &merchant_pub,
+            .address = address,
+            .jurisdiction = jurisdiction,
+            .default_max_wire_fee = &default_max_wire_fee,
+            .default_wire_fee_amortization = default_wire_fee_amortization,
+            .default_max_deposit_fee = &default_max_deposit_fee,
+            .default_wire_transfer_delay = default_wire_transfer_delay,
+            .default_pay_delay = default_pay_delay
+          };
+
+          igh->cb (igh->cb_cls,
+                   &hr,
+                   accounts_length,
+                   aa,
+                   &details);
+          GNUNET_JSON_parse_free (spec);
+          TALER_MERCHANT_instance_get_cancel (igh);
+          return;
+        }
+      }
+      GNUNET_break_op (0);
+      hr.http_status = 0;
+      hr.ec = TALER_EC_INVALID_RESPONSE;
+      GNUNET_JSON_parse_free (spec);
+      break;
+    }
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  igh->cb (igh->cb_cls,
+           &hr,
+           0,
+           NULL,
+           NULL);
+  TALER_MERCHANT_instance_get_cancel (igh);
+}
+
+
+/**
+ * Get the instance data of a backend. Will connect to the merchant backend
+ * and obtain information about the instances.  The respective information will
+ * be passed to the @a cb once available.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id identity of the instance to get information about
+ * @param cb function to call with the
+ *        backend's instances information
+ * @param cb_cls closure for @a cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstanceGetHandle *
+TALER_MERCHANT_instance_get (struct GNUNET_CURL_Context *ctx,
+                             const char *backend_url,
+                             const char *instance_id,
+                             TALER_MERCHANT_InstanceGetCallback cb,
+                             void *cb_cls)
+{
+  struct TALER_MERCHANT_InstanceGetHandle *igh;
+  CURL *eh;
+
+  igh = GNUNET_new (struct TALER_MERCHANT_InstanceGetHandle);
+  igh->ctx = ctx;
+  igh->cb = cb;
+  igh->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/instances/%s",
+                     instance_id);
+    igh->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == igh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (igh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              igh->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   igh->url));
+  igh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_get_instance_finished,
+                                  igh);
+  return igh;
+}
+
+
+/**
+ * Cancel GET /instance/$ID request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param igh request to cancel.
+ */
+void
+TALER_MERCHANT_instance_get_cancel (
+  struct TALER_MERCHANT_InstanceGetHandle *igh)
+{
+  if (NULL != igh->job)
+    GNUNET_CURL_job_cancel (igh->job);
+  GNUNET_free (igh->url);
+  GNUNET_free (igh);
+}
diff --git a/src/lib/merchant_api_get_instances.c 
b/src/lib/merchant_api_get_instances.c
new file mode 100644
index 0000000..604267c
--- /dev/null
+++ b/src/lib/merchant_api_get_instances.c
@@ -0,0 +1,292 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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_get_instances.c
+ * @brief Implementation of the GET /instances request of the merchant's HTTP 
API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /instances operation.
+ */
+struct TALER_MERCHANT_InstancesGetHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_InstancesGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Parse instance information from @a ia.
+ *
+ * @param ia JSON array (or NULL!) with instance data
+ * @param igh operation handle
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_instances (const json_t *ia,
+                 struct TALER_MERCHANT_InstancesGetHandle *igh)
+{
+  unsigned int iis_len = json_array_size (ia);
+  struct TALER_MERCHANT_InstanceInformation iis[iis_len];
+  size_t index;
+  json_t *value;
+  int ret;
+
+  ret = GNUNET_OK;
+  json_array_foreach (ia, index, value) {
+    struct TALER_MERCHANT_InstanceInformation *ii = &iis[index];
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("name",
+                               &ii->name),
+      GNUNET_JSON_spec_string ("id",
+                               &ii->id),
+      GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                   &ii->merchant_pub),
+      GNUNET_JSON_spec_json ("payment_targets",
+                             &ii->payment_targets),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      ret = GNUNET_SYSERR;
+      continue;
+    }
+    if (! json_is_array (ii->payment_targets))
+    {
+      GNUNET_break_op (0);
+      ret = GNUNET_SYSERR;
+      break;
+    }
+    for (unsigned int i = 0; i<json_array_size (ii->payment_targets); i++)
+    {
+      if  (! json_is_string (json_array_get (ii->payment_targets,
+                                             i)))
+      {
+        GNUNET_break_op (0);
+        ret = GNUNET_SYSERR;
+        break;
+      }
+    }
+    if (GNUNET_SYSERR == ret)
+      break;
+  }
+  if (GNUNET_OK == ret)
+  {
+    struct TALER_MERCHANT_HttpResponse hr = {
+      .http_status = MHD_HTTP_OK
+    };
+
+    igh->cb (igh->cb_cls,
+             &hr,
+             iis_len,
+             iis);
+    igh->cb = NULL; /* just to be sure */
+  }
+  for (unsigned int i = 0; i<iis_len; i++)
+    if (NULL != iis[i].payment_targets)
+      json_decref (iis[i].payment_targets);
+  return ret;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /instances request.
+ *
+ * @param cls the `struct TALER_MERCHANT_InstancesGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_instances_finished (void *cls,
+                           long response_code,
+                           const void *response)
+{
+  struct TALER_MERCHANT_InstancesGetHandle *igh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  igh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /instances response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    {
+      json_t *instances;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("instances",
+                               &instances),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+      }
+      else
+      {
+        if ( (! json_is_array (instances)) ||
+             (GNUNET_OK ==
+              parse_instances (instances,
+                               igh)) )
+        {
+          GNUNET_JSON_parse_free (spec);
+          TALER_MERCHANT_instances_get_cancel (igh);
+          return;
+        }
+        else
+        {
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+        }
+      }
+      GNUNET_JSON_parse_free (spec);
+      break;
+    }
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  igh->cb (igh->cb_cls,
+           &hr,
+           0,
+           NULL);
+  TALER_MERCHANT_instances_get_cancel (igh);
+}
+
+
+/**
+ * Get the instance data of a backend. Will connect to the merchant backend
+ * and obtain information about the instances.  The respective information will
+ * be passed to the @a instances_cb once available.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instances_cb function to call with the
+ *        backend's instances information
+ * @param instances_cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstancesGetHandle *
+TALER_MERCHANT_instances_get (struct GNUNET_CURL_Context *ctx,
+                              const char *backend_url,
+                              TALER_MERCHANT_InstancesGetCallback instances_cb,
+                              void *instances_cb_cls)
+{
+  struct TALER_MERCHANT_InstancesGetHandle *igh;
+  CURL *eh;
+
+  igh = GNUNET_new (struct TALER_MERCHANT_InstancesGetHandle);
+  igh->ctx = ctx;
+  igh->cb = instances_cb;
+  igh->cb_cls = instances_cb_cls;
+  igh->url = TALER_url_join (backend_url,
+                             "private/instances",
+                             NULL);
+  if (NULL == igh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (igh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              igh->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   igh->url));
+  igh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_instances_finished,
+                                  igh);
+  return igh;
+}
+
+
+/**
+ * Cancel /instances request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param igh request to cancel.
+ */
+void
+TALER_MERCHANT_instances_get_cancel (
+  struct TALER_MERCHANT_InstancesGetHandle *igh)
+{
+  if (NULL != igh->job)
+    GNUNET_CURL_job_cancel (igh->job);
+  GNUNET_free (igh->url);
+  GNUNET_free (igh);
+}
diff --git a/src/lib/merchant_api_get_orders.c 
b/src/lib/merchant_api_get_orders.c
new file mode 100644
index 0000000..df96c57
--- /dev/null
+++ b/src/lib/merchant_api_get_orders.c
@@ -0,0 +1,386 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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_get_orders.c
+ * @brief Implementation of the GET /orders request of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /orders operation.
+ */
+struct TALER_MERCHANT_OrdersGetHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_OrdersGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Parse order information from @a ia.
+ *
+ * @param ia JSON array (or NULL!) with order data
+ * @param ogh operation handle
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_orders (const json_t *ia,
+              struct TALER_MERCHANT_OrdersGetHandle *ogh)
+{
+  unsigned int oes_len = json_array_size (ia);
+  struct TALER_MERCHANT_OrderEntry oes[oes_len];
+  size_t index;
+  json_t *value;
+  int ret;
+
+  ret = GNUNET_OK;
+  json_array_foreach (ia, index, value) {
+    struct TALER_MERCHANT_OrderEntry *ie = &oes[index];
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("order_id",
+                               &ie->order_id),
+      GNUNET_JSON_spec_absolute_time ("timestamp",
+                                      &ie->timestamp),
+      GNUNET_JSON_spec_uint64 ("row_id",
+                               &ie->order_serial),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      ret = GNUNET_SYSERR;
+      continue;
+    }
+    if (GNUNET_SYSERR == ret)
+      break;
+  }
+  if (GNUNET_OK == ret)
+  {
+    struct TALER_MERCHANT_HttpResponse hr = {
+      .http_status = MHD_HTTP_OK
+    };
+
+    ogh->cb (ogh->cb_cls,
+             &hr,
+             oes_len,
+             oes);
+    ogh->cb = NULL; /* just to be sure */
+  }
+  return ret;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /orders request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OrdersGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_get_orders_finished (void *cls,
+                            long response_code,
+                            const void *response)
+{
+  struct TALER_MERCHANT_OrdersGetHandle *ogh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  ogh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /orders response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    {
+      json_t *orders;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("orders",
+                               &orders),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+      }
+      else
+      {
+        if ( (! json_is_array (orders)) ||
+             (GNUNET_OK ==
+              parse_orders (orders,
+                            ogh)) )
+        {
+          GNUNET_JSON_parse_free (spec);
+          TALER_MERCHANT_orders_get_cancel (ogh);
+          return;
+        }
+        else
+        {
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+        }
+      }
+      GNUNET_JSON_parse_free (spec);
+      break;
+    }
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  ogh->cb (ogh->cb_cls,
+           &hr,
+           0,
+           NULL);
+  TALER_MERCHANT_orders_get_cancel (ogh);
+}
+
+
+/**
+ * Make a GET /orders request.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend's inventory information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_OrdersGetHandle *
+TALER_MERCHANT_orders_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  TALER_MERCHANT_OrdersGetCallback cb,
+  void *cb_cls)
+{
+  return TALER_MERCHANT_orders_get2 (ctx,
+                                     backend_url,
+                                     TALER_EXCHANGE_YNA_ALL,
+                                     TALER_EXCHANGE_YNA_ALL,
+                                     TALER_EXCHANGE_YNA_ALL,
+                                     GNUNET_TIME_UNIT_FOREVER_ABS,
+                                     UINT64_MAX,
+                                     -20, /* default is most recent 20 entries 
*/
+                                     GNUNET_TIME_UNIT_ZERO,
+                                     cb,
+                                     cb_cls);
+}
+
+
+/**
+ * Make a GET /orders request with filters.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param paid filter on payment status
+ * @param refunded filter on refund status
+ * @param wired filter on wire transfer status
+ * @param date range limit by date
+ * @param start_row range limit by order table row
+ * @param delta range from which @a date and @a start_row apply, positive
+ *              to return delta items after the given limit(s), negative to
+ *              return delta items before the given limit(s)
+ * @param timeout how long to wait (long polling) of zero results match the 
query
+ * @param cb function to call with the backend's inventory information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_OrdersGetHandle *
+TALER_MERCHANT_orders_get2 (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  enum TALER_EXCHANGE_YesNoAll paid,
+  enum TALER_EXCHANGE_YesNoAll refunded,
+  enum TALER_EXCHANGE_YesNoAll wired,
+  struct GNUNET_TIME_Absolute date,
+  uint64_t start_row,
+  int64_t delta,
+  struct GNUNET_TIME_Relative timeout,
+  TALER_MERCHANT_OrdersGetCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_OrdersGetHandle *ogh;
+  CURL *eh;
+  unsigned int timeout_ms = timeout.rel_value_us
+                            / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+
+  GNUNET_assert (NULL != backend_url);
+  if (0 == delta)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  ogh = GNUNET_new (struct TALER_MERCHANT_OrdersGetHandle);
+  ogh->ctx = ctx;
+  ogh->cb = cb;
+  ogh->cb_cls = cb_cls;
+
+  /* build ogh->url with the various optional arguments */
+  {
+    const char *dstr;
+    bool have_date;
+    bool have_srow;
+    char cbuf[30];
+    char dbuf[30];
+    char tbuf[30];
+
+    GNUNET_snprintf (tbuf,
+                     sizeof (tbuf),
+                     "%llu",
+                     (unsigned long long) timeout_ms);
+    GNUNET_snprintf (dbuf,
+                     sizeof (dbuf),
+                     "%lld",
+                     (long long) delta);
+    GNUNET_snprintf (cbuf,
+                     sizeof (cbuf),
+                     "%llu",
+                     (unsigned long long) start_row);
+    dstr = GNUNET_STRINGS_absolute_time_to_string (date);
+    if (delta > 0)
+    {
+      have_date = (0 != date.abs_value_us);
+      have_srow = (0 != start_row);
+    }
+    else
+    {
+      have_date = (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+                   date.abs_value_us);
+      have_srow = (UINT64_MAX != start_row);
+    }
+    ogh->url = TALER_url_join (backend_url,
+                               "private/orders",
+                               "paid",
+                               (TALER_EXCHANGE_YNA_ALL != paid)
+                               ? TALER_yna_to_string (paid)
+                               : NULL,
+                               "refunded",
+                               (TALER_EXCHANGE_YNA_ALL != refunded)
+                               ? TALER_yna_to_string (refunded)
+                               : NULL,
+                               "wired",
+                               (TALER_EXCHANGE_YNA_ALL != wired)
+                               ? TALER_yna_to_string (wired)
+                               : NULL,
+                               "date",
+                               (have_date)
+                               ? dstr
+                               : NULL,
+                               "start",
+                               (have_srow)
+                               ? cbuf
+                               : NULL,
+                               "delta",
+                               (-20 != delta)
+                               ? dbuf
+                               : NULL,
+                               "timeout_ms",
+                               (0 != timeout_ms)
+                               ? tbuf
+                               : NULL,
+                               NULL);
+  }
+  if (NULL == ogh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (ogh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              ogh->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   ogh->url));
+  ogh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_get_orders_finished,
+                                  ogh);
+  return ogh;
+}
+
+
+/**
+ * Cancel /orders request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param ogh request to cancel.
+ */
+void
+TALER_MERCHANT_orders_get_cancel (
+  struct TALER_MERCHANT_OrdersGetHandle *ogh)
+{
+  if (NULL != ogh->job)
+    GNUNET_CURL_job_cancel (ogh->job);
+  GNUNET_free (ogh->url);
+  GNUNET_free (ogh);
+}
diff --git a/src/lib/merchant_api_get_product.c 
b/src/lib/merchant_api_get_product.c
new file mode 100644
index 0000000..e9263b0
--- /dev/null
+++ b/src/lib/merchant_api_get_product.c
@@ -0,0 +1,278 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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_get_product.c
+ * @brief Implementation of the GET /product/$ID request of the merchant's 
HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /products/$ID operation.
+ */
+struct TALER_MERCHANT_ProductGetHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ProductGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /products/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ProductGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_get_product_finished (void *cls,
+                             long response_code,
+                             const void *response)
+{
+  struct TALER_MERCHANT_ProductGetHandle *pgh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  pgh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /products/$ID response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    {
+      const char *description;
+      json_t *description_i18n;
+      const char *unit;
+      struct TALER_Amount price;
+      json_t *image;
+      json_t *taxes;
+      int64_t total_stock;
+      uint64_t total_sold;
+      uint64_t total_lost;
+      json_t *address;
+      bool rst_ok = true;
+      struct GNUNET_TIME_Absolute next_restock = {0};
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_string ("description",
+                                 &description),
+        GNUNET_JSON_spec_json ("description_i18n",
+                               &description_i18n),
+        GNUNET_JSON_spec_string ("unit",
+                                 &unit),
+        TALER_JSON_spec_amount ("price",
+                                &price),
+        GNUNET_JSON_spec_json ("image",
+                               &image),
+        GNUNET_JSON_spec_json ("taxes",
+                               &taxes),
+        GNUNET_JSON_spec_int64 ("total_stock",
+                                &total_stock),
+        GNUNET_JSON_spec_uint64 ("total_sold",
+                                 &total_sold),
+        GNUNET_JSON_spec_uint64 ("total_lost",
+                                 &total_lost),
+        GNUNET_JSON_spec_json ("address",
+                               &address),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (NULL !=
+          json_object_get (json,
+                           "next_restock"))
+      {
+        struct GNUNET_JSON_Specification spect[] = {
+          GNUNET_JSON_spec_absolute_time ("next_restock",
+                                          &next_restock),
+          GNUNET_JSON_spec_end ()
+        };
+
+        if (GNUNET_OK !=
+            GNUNET_JSON_parse (json,
+                               spect,
+                               NULL, NULL))
+          rst_ok = false;
+      }
+
+
+      if ( (rst_ok) &&
+           (GNUNET_OK ==
+            GNUNET_JSON_parse (json,
+                               spec,
+                               NULL, NULL)) )
+      {
+        pgh->cb (pgh->cb_cls,
+                 &hr,
+                 description,
+                 description_i18n,
+                 unit,
+                 &price,
+                 image,
+                 taxes,
+                 total_stock,
+                 total_sold,
+                 total_lost,
+                 address,
+                 next_restock);
+        GNUNET_JSON_parse_free (spec);
+        TALER_MERCHANT_product_get_cancel (pgh);
+        return;
+      }
+      hr.http_status = 0;
+      hr.ec = TALER_EC_INVALID_RESPONSE;
+      GNUNET_JSON_parse_free (spec);
+      break;
+    }
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  pgh->cb (pgh->cb_cls,
+           &hr,
+           NULL,
+           NULL,
+           NULL,
+           NULL,
+           NULL,
+           NULL,
+           0,
+           0,
+           0,
+           NULL,
+           GNUNET_TIME_UNIT_FOREVER_ABS);
+  TALER_MERCHANT_product_get_cancel (pgh);
+}
+
+
+/**
+ * Make a GET /product/$ID request to get details about an
+ * individual product.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier of the product to inquire about
+ * @param cb function to call with the backend's product information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductGetHandle *
+TALER_MERCHANT_product_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  TALER_MERCHANT_ProductGetCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_ProductGetHandle *pgh;
+  CURL *eh;
+
+  pgh = GNUNET_new (struct TALER_MERCHANT_ProductGetHandle);
+  pgh->ctx = ctx;
+  pgh->cb = cb;
+  pgh->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/products/%s",
+                     product_id);
+    pgh->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == pgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (pgh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              pgh->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   pgh->url));
+  pgh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_get_product_finished,
+                                  pgh);
+  return pgh;
+}
+
+
+/**
+ * Cancel GET /product/$ID request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param pgh request to cancel.
+ */
+void
+TALER_MERCHANT_product_get_cancel (
+  struct TALER_MERCHANT_ProductGetHandle *pgh)
+{
+  if (NULL != pgh->job)
+    GNUNET_CURL_job_cancel (pgh->job);
+  GNUNET_free (pgh->url);
+  GNUNET_free (pgh);
+}
diff --git a/src/lib/merchant_api_get_products.c 
b/src/lib/merchant_api_get_products.c
new file mode 100644
index 0000000..c3edbde
--- /dev/null
+++ b/src/lib/merchant_api_get_products.c
@@ -0,0 +1,265 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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_get_products.c
+ * @brief Implementation of the GET /products request of the merchant's HTTP 
API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Handle for a GET /products operation.
+ */
+struct TALER_MERCHANT_ProductsGetHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ProductsGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Parse product information from @a ia.
+ *
+ * @param ia JSON array (or NULL!) with product data
+ * @param pgh operation handle
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_products (const json_t *ia,
+                struct TALER_MERCHANT_ProductsGetHandle *pgh)
+{
+  unsigned int ies_len = json_array_size (ia);
+  struct TALER_MERCHANT_InventoryEntry ies[ies_len];
+  size_t index;
+  json_t *value;
+  int ret;
+
+  ret = GNUNET_OK;
+  json_array_foreach (ia, index, value) {
+    struct TALER_MERCHANT_InventoryEntry *ie = &ies[index];
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("product_id",
+                               &ie->product_id),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      ret = GNUNET_SYSERR;
+      continue;
+    }
+    if (GNUNET_SYSERR == ret)
+      break;
+  }
+  if (GNUNET_OK == ret)
+  {
+    struct TALER_MERCHANT_HttpResponse hr = {
+      .http_status = MHD_HTTP_OK
+    };
+
+    pgh->cb (pgh->cb_cls,
+             &hr,
+             ies_len,
+             ies);
+    pgh->cb = NULL; /* just to be sure */
+  }
+  return ret;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /products request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ProductsGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_get_products_finished (void *cls,
+                              long response_code,
+                              const void *response)
+{
+  struct TALER_MERCHANT_ProductsGetHandle *pgh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  pgh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /products response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    {
+      json_t *products;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("products",
+                               &products),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+      }
+      else
+      {
+        if ( (! json_is_array (products)) ||
+             (GNUNET_OK ==
+              parse_products (products,
+                              pgh)) )
+        {
+          GNUNET_JSON_parse_free (spec);
+          TALER_MERCHANT_products_get_cancel (pgh);
+          return;
+        }
+        else
+        {
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+        }
+      }
+      GNUNET_JSON_parse_free (spec);
+      break;
+    }
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  pgh->cb (pgh->cb_cls,
+           &hr,
+           0,
+           NULL);
+  TALER_MERCHANT_products_get_cancel (pgh);
+}
+
+
+/**
+ * Make a GET /products request.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend's inventory information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductsGetHandle *
+TALER_MERCHANT_products_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  TALER_MERCHANT_ProductsGetCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_ProductsGetHandle *pgh;
+  CURL *eh;
+
+  pgh = GNUNET_new (struct TALER_MERCHANT_ProductsGetHandle);
+  pgh->ctx = ctx;
+  pgh->cb = cb;
+  pgh->cb_cls = cb_cls;
+  pgh->url = TALER_url_join (backend_url,
+                             "private/products",
+                             NULL);
+  if (NULL == pgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (pgh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              pgh->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   pgh->url));
+  pgh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_get_products_finished,
+                                  pgh);
+  return pgh;
+}
+
+
+/**
+ * Cancel /products request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param pgh request to cancel.
+ */
+void
+TALER_MERCHANT_products_get_cancel (
+  struct TALER_MERCHANT_ProductsGetHandle *pgh)
+{
+  if (NULL != pgh->job)
+    GNUNET_CURL_job_cancel (pgh->job);
+  GNUNET_free (pgh->url);
+  GNUNET_free (pgh);
+}
diff --git a/src/lib/merchant_api_get_reserve.c 
b/src/lib/merchant_api_get_reserve.c
new file mode 100644
index 0000000..3ff14d4
--- /dev/null
+++ b/src/lib/merchant_api_get_reserve.c
@@ -0,0 +1,317 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017, 2020 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_get_reserve.c
+ * @brief Implementation of the GET /reserve 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>
+
+
+/**
+ * @brief A Handle for tracking wire reserve.
+ */
+struct TALER_MERCHANT_ReserveGetHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ReserveGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /reserve request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ReserveGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_reserve_get_finished (void *cls,
+                             long response_code,
+                             const void *response)
+{
+  struct TALER_MERCHANT_ReserveGetHandle *rgh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+  bool active;
+
+  rgh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      struct TALER_MERCHANT_ReserveSummary rs;
+      const json_t *tips;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_absolute_time ("creation_time",
+                                        &rs.creation_time),
+        GNUNET_JSON_spec_absolute_time ("expiration_time",
+                                        &rs.expiration_time),
+        GNUNET_JSON_spec_bool ("active",
+                               &active),
+        TALER_JSON_spec_amount ("merchant_initial_amount",
+                                &rs.merchant_initial_amount),
+        TALER_JSON_spec_amount ("exchange_initial_amount",
+                                &rs.exchange_initial_amount),
+        TALER_JSON_spec_amount ("pickup_amount",
+                                &rs.pickup_amount),
+        TALER_JSON_spec_amount ("committed_amount",
+                                &rs.committed_amount),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        break;
+      }
+
+      tips = json_object_get (json,
+                              "tips");
+      if ((NULL == tips) ||
+          json_is_null (tips))
+      {
+        rgh->cb (rgh->cb_cls,
+                 &hr,
+                 &rs,
+                 false,
+                 0,
+                 NULL);
+        TALER_MERCHANT_reserve_get_cancel (rgh);
+        return;
+      }
+      if (! json_is_array (tips))
+      {
+        GNUNET_break_op (0);
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        break;
+      }
+      {
+        size_t tds_length;
+        json_t *tip;
+        struct TALER_MERCHANT_TipDetails *tds;
+        unsigned int i;
+        bool ok;
+
+        tds_length = json_array_size (tips);
+        tds = GNUNET_new_array (tds_length,
+                                struct TALER_MERCHANT_TipDetails);
+        ok = true;
+        json_array_foreach (tips, i, tip) {
+          struct TALER_MERCHANT_TipDetails *td = &tds[i];
+          struct GNUNET_JSON_Specification ispec[] = {
+            GNUNET_JSON_spec_fixed_auto ("tip_id",
+                                         &td->tip_id),
+            TALER_JSON_spec_amount ("total_amount",
+                                    &td->amount),
+            GNUNET_JSON_spec_string ("reason",
+                                     &td->reason),
+            GNUNET_JSON_spec_end ()
+          };
+
+          if (GNUNET_OK !=
+              GNUNET_JSON_parse (tip,
+                                 ispec,
+                                 NULL, NULL))
+          {
+            GNUNET_break_op (0);
+            ok = false;
+            break;
+          }
+        }
+
+        if (! ok)
+        {
+          GNUNET_break_op (0);
+          GNUNET_free (tds);
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+          break;
+        }
+        rgh->cb (rgh->cb_cls,
+                 &hr,
+                 &rs,
+                 active,
+                 tds_length,
+                 tds);
+        GNUNET_free (tds);
+        TALER_MERCHANT_reserve_get_cancel (rgh);
+        return;
+      }
+    }
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    response_code = 0;
+    break;
+  }
+  rgh->cb (rgh->cb_cls,
+           &hr,
+           NULL,
+           false,
+           0,
+           NULL);
+  TALER_MERCHANT_reserve_get_cancel (rgh);
+}
+
+
+/**
+ * Issue a GET /reserve/$RESERVE_ID request to the backend.  Queries the 
backend
+ * about the status of a reserve.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param reserve_pub which reserve should be queried
+ * @param tips should we return details about the tips issued from the reserve
+ * @param cb function to call with the result(s)
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_ReserveGetHandle *
+TALER_MERCHANT_reserve_get (struct GNUNET_CURL_Context *ctx,
+                            const char *backend_url,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            bool fetch_tips,
+                            TALER_MERCHANT_ReserveGetCallback cb,
+                            void *cb_cls)
+{
+  struct TALER_MERCHANT_ReserveGetHandle *rgh;
+  CURL *eh;
+
+  rgh = GNUNET_new (struct TALER_MERCHANT_ReserveGetHandle);
+  rgh->ctx = ctx;
+  rgh->cb = cb;
+  rgh->cb_cls = cb_cls;
+  {
+    char res_str[sizeof (*reserve_pub) * 2];
+    char arg_str[sizeof (res_str) + 32];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (reserve_pub,
+                                         sizeof (*reserve_pub),
+                                         res_str,
+                                         sizeof (res_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "private/reserves/%s",
+                     res_str);
+    rgh->url = TALER_url_join (backend_url,
+                               arg_str,
+                               "tips",
+                               fetch_tips ? "yes" : "no",
+                               NULL);
+  }
+  if (NULL == rgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (rgh);
+    return NULL;
+  }
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   rgh->url));
+  rgh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_reserve_get_finished,
+                                  rgh);
+  return rgh;
+}
+
+
+/**
+ * Cancel a GET /reserves/$RESERVE_ID request.
+ *
+ * @param rgh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_reserve_get_cancel (
+  struct TALER_MERCHANT_ReserveGetHandle *rgh)
+{
+  if (NULL != rgh->job)
+  {
+    GNUNET_CURL_job_cancel (rgh->job);
+    rgh->job = NULL;
+  }
+  GNUNET_free (rgh->url);
+  GNUNET_free (rgh);
+}
+
+
+/* end of merchant_api_get_reserve.c */
diff --git a/src/lib/merchant_api_get_reserves.c 
b/src/lib/merchant_api_get_reserves.c
new file mode 100644
index 0000000..f524514
--- /dev/null
+++ b/src/lib/merchant_api_get_reserves.c
@@ -0,0 +1,300 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017, 2020 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_get_reserves.c
+ * @brief Implementation of the GET /reserves 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>
+
+
+/**
+ * @brief A Handle for tracking wire reserves.
+ */
+struct TALER_MERCHANT_ReservesGetHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ReservesGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /reserves request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ReservesGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_reserves_get_finished (void *cls,
+                              long response_code,
+                              const void *response)
+{
+  struct TALER_MERCHANT_ReservesGetHandle *rgh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  rgh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      json_t *reserves;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("reserves",
+                               &reserves),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        break;
+      }
+      else
+      {
+        size_t rds_length;
+        struct TALER_MERCHANT_ReserveSummary *rds;
+        json_t *reserve;
+        unsigned int i;
+        bool ok;
+
+        if (! json_is_array (reserves))
+        {
+          GNUNET_break_op (0);
+          GNUNET_JSON_parse_free (spec);
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+          break;
+        }
+        rds_length = json_array_size (reserves);
+        rds = GNUNET_new_array (rds_length,
+                                struct TALER_MERCHANT_ReserveSummary);
+        ok = true;
+        json_array_foreach (reserves, i, reserve) {
+          struct TALER_MERCHANT_ReserveSummary *rd = &rds[i];
+          struct GNUNET_JSON_Specification ispec[] = {
+            GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                         &rd->reserve_pub),
+            GNUNET_JSON_spec_absolute_time ("creation_time",
+                                            &rd->creation_time),
+            GNUNET_JSON_spec_absolute_time ("expiration_time",
+                                            &rd->expiration_time),
+            TALER_JSON_spec_amount ("merchant_initial_amount",
+                                    &rd->merchant_initial_amount),
+            TALER_JSON_spec_amount ("exchange_initial_amount",
+                                    &rd->exchange_initial_amount),
+            TALER_JSON_spec_amount ("pickup_amount",
+                                    &rd->pickup_amount),
+            TALER_JSON_spec_amount ("committed_amount",
+                                    &rd->committed_amount),
+            GNUNET_JSON_spec_bool ("active",
+                                   &rd->active),
+            GNUNET_JSON_spec_end ()
+          };
+
+          if (GNUNET_OK !=
+              GNUNET_JSON_parse (reserve,
+                                 ispec,
+                                 NULL, NULL))
+          {
+            GNUNET_break_op (0);
+            ok = false;
+            break;
+          }
+        }
+
+        if (! ok)
+        {
+          GNUNET_break_op (0);
+          GNUNET_free (rds);
+          GNUNET_JSON_parse_free (spec);
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+          break;
+        }
+        rgh->cb (rgh->cb_cls,
+                 &hr,
+                 rds_length,
+                 rds);
+        GNUNET_free (rds);
+        GNUNET_JSON_parse_free (spec);
+        TALER_MERCHANT_reserves_get_cancel (rgh);
+        return;
+      }
+    }
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    response_code = 0;
+    break;
+  }
+  rgh->cb (rgh->cb_cls,
+           &hr,
+           0,
+           NULL);
+  TALER_MERCHANT_reserves_get_cancel (rgh);
+}
+
+
+/**
+ * Issue a GET /reserves request to the backend.  Informs the backend
+ * that a customer wants to pick up a reserves.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param after filter for reserves created after this date, use 0 for no 
filtering
+ * @param active filter for reserves that are active
+ * @param failures filter for reserves where we disagree about the balance with
+ *        the exchange
+ * @param cb function to call with the result(s)
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_ReservesGetHandle *
+TALER_MERCHANT_reserves_get (struct GNUNET_CURL_Context *ctx,
+                             const char *backend_url,
+                             struct GNUNET_TIME_Absolute after,
+                             enum TALER_EXCHANGE_YesNoAll active,
+                             enum TALER_EXCHANGE_YesNoAll failures,
+                             TALER_MERCHANT_ReservesGetCallback cb,
+                             void *cb_cls)
+{
+  struct TALER_MERCHANT_ReservesGetHandle *rgh;
+  CURL *eh;
+  const char *active_s = NULL;
+  const char *failures_s = NULL;
+  char *after_s;
+
+  rgh = GNUNET_new (struct TALER_MERCHANT_ReservesGetHandle);
+  rgh->ctx = ctx;
+  rgh->cb = cb;
+  rgh->cb_cls = cb_cls;
+  active_s = TALER_yna_to_string (active);
+  failures_s = TALER_yna_to_string (failures);
+  after_s = GNUNET_strdup (GNUNET_STRINGS_absolute_time_to_string (
+                             after));
+  rgh->url = TALER_url_join (backend_url,
+                             "private/reserves",
+                             "active",
+                             active_s,
+                             "failures",
+                             failures_s,
+                             "after",
+                             after.abs_value_us != 0
+                             ? after_s
+                             : NULL,
+                             NULL);
+  GNUNET_free (after_s);
+  if (NULL == rgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (rgh);
+    return NULL;
+  }
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   rgh->url));
+  rgh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_reserves_get_finished,
+                                  rgh);
+  return rgh;
+}
+
+
+/**
+ * Cancel a GET /reserves request.
+ *
+ * @param rgh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_reserves_get_cancel (
+  struct TALER_MERCHANT_ReservesGetHandle *rgh)
+{
+  if (NULL != rgh->job)
+  {
+    GNUNET_CURL_job_cancel (rgh->job);
+    rgh->job = NULL;
+  }
+  GNUNET_free (rgh->url);
+  GNUNET_free (rgh);
+}
+
+
+/* end of merchant_api_get_reserves.c */
diff --git a/src/lib/merchant_api_get_tips.c b/src/lib/merchant_api_get_tips.c
new file mode 100644
index 0000000..ee0ad1b
--- /dev/null
+++ b/src/lib/merchant_api_get_tips.c
@@ -0,0 +1,333 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_get_tips.c
+ * @brief Implementation of the GET /private/tips request of the merchant's 
HTTP API
+ * @author Jonathan Buchanan
+ */
+#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>
+
+
+/**
+ * Handle for a GET /private/tips operation.
+ */
+struct TALER_MERCHANT_TipsGetHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_TipsGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Parse tip information from @a ia.
+ *
+ * @param ia JSON array (or NULL!) tip order data
+ * @param tgh operation handle
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_tips (const json_t *ia,
+            struct TALER_MERCHANT_TipsGetHandle *tgh)
+{
+  unsigned int tes_len = json_array_size (ia);
+  struct TALER_MERCHANT_TipEntry tes[tes_len];
+  size_t index;
+  json_t *value;
+  int ret;
+
+  ret = GNUNET_OK;
+  json_array_foreach (ia, index, value) {
+    struct TALER_MERCHANT_TipEntry *ie = &tes[index];
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_uint64 ("row_id",
+                               &ie->row_id),
+      GNUNET_JSON_spec_fixed_auto ("tip_id",
+                                   &ie->tip_id),
+      TALER_JSON_spec_amount ("tip_amount",
+                              &ie->tip_amount),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      ret = GNUNET_SYSERR;
+      continue;
+    }
+    if (GNUNET_SYSERR == ret)
+      break;
+  }
+  if (GNUNET_OK == ret)
+  {
+    struct TALER_MERCHANT_HttpResponse hr = {
+      .http_status = MHD_HTTP_OK
+    };
+
+    tgh->cb (tgh->cb_cls,
+             &hr,
+             tes_len,
+             tes);
+    tgh->cb = NULL; /* just to be sure */
+  }
+  return ret;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /private/tips request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TipsGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_get_tips_finished (void *cls,
+                          long response_code,
+                          const void *response)
+{
+  struct TALER_MERCHANT_TipsGetHandle *tgh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  tgh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /private/tips response with status code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    {
+      json_t *tips;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("tips",
+                               &tips),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+      }
+      else
+      {
+        if ( (! json_is_array (tips)) ||
+             (GNUNET_OK ==
+              parse_tips (tips,
+                          tgh)) )
+        {
+          GNUNET_JSON_parse_free (spec);
+          TALER_MERCHANT_tips_get_cancel (tgh);
+          return;
+        }
+        else
+        {
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+        }
+      }
+      GNUNET_JSON_parse_free (spec);
+      break;
+    }
+  default:
+    /* unexpected response code */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  tgh->cb (tgh->cb_cls,
+           &hr,
+           0,
+           NULL);
+  TALER_MERCHANT_tips_get_cancel (tgh);
+}
+
+
+/**
+ * Make a GET /private/tips request.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param cb function to call with the backend's tip information
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_TipsGetHandle *
+TALER_MERCHANT_tips_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  TALER_MERCHANT_TipsGetCallback cb,
+  void *cb_cls)
+{
+  return TALER_MERCHANT_tips_get2 (ctx,
+                                   backend_url,
+                                   TALER_EXCHANGE_YNA_NO,
+                                   -20,
+                                   UINT64_MAX,
+                                   cb,
+                                   cb_cls);
+}
+
+
+/**
+ * Issue a GET /private/tips request with filters to the backend.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param include_expired whether to return all tips or only unexpired tips
+ * @param limit number of results to return, negative for descending row id, 
positive for ascending
+ * @param offset row id to start returning results from
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipsGetHandle *
+TALER_MERCHANT_tips_get2 (struct GNUNET_CURL_Context *ctx,
+                          const char *backend_url,
+                          enum TALER_EXCHANGE_YesNoAll expired,
+                          int64_t limit,
+                          uint64_t offset,
+                          TALER_MERCHANT_TipsGetCallback cb,
+                          void *cb_cls)
+{
+  struct TALER_MERCHANT_TipsGetHandle *tgh;
+  CURL *eh;
+
+  GNUNET_assert (NULL != backend_url);
+  if (0 == limit)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  tgh = GNUNET_new (struct TALER_MERCHANT_TipsGetHandle);
+  tgh->ctx = ctx;
+  tgh->cb = cb;
+  tgh->cb_cls = cb_cls;
+
+  /* build tgh->url with the various optional arguments */
+  {
+    char cbuf[30];
+    char lbuf[30];
+    bool have_offset;
+
+    GNUNET_snprintf (lbuf,
+                     sizeof (lbuf),
+                     "%lld",
+                     (long long) limit);
+
+    if (limit > 0)
+      have_offset = (0 != offset);
+    else
+      have_offset = (UINT64_MAX != offset);
+    GNUNET_snprintf (cbuf,
+                     sizeof (cbuf),
+                     "%llu",
+                     (unsigned long long) offset);
+    tgh->url = TALER_url_join (backend_url,
+                               "private/tips",
+                               "expired",
+                               (TALER_EXCHANGE_YNA_ALL != expired)
+                               ? TALER_yna_to_string (expired)
+                               : NULL,
+                               "offset", (have_offset) ? cbuf : NULL,
+                               "limit", (-20 != limit) ? lbuf : NULL,
+                               NULL);
+  }
+  if (NULL == tgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (tgh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting URL '%s'\n",
+              tgh->url);
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   tgh->url));
+  tgh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_get_tips_finished,
+                                  tgh);
+  return tgh;
+}
+
+
+/**
+ * Cancel GET /private/tips request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param ogh request to cancel.
+ */
+void
+TALER_MERCHANT_tips_get_cancel (
+  struct TALER_MERCHANT_TipsGetHandle *tgh)
+{
+  if (NULL != tgh->job)
+    GNUNET_CURL_job_cancel (tgh->job);
+  GNUNET_free (tgh->url);
+  GNUNET_free (tgh);
+}
diff --git a/src/lib/merchant_api_get_transfers.c 
b/src/lib/merchant_api_get_transfers.c
new file mode 100644
index 0000000..5d08dba
--- /dev/null
+++ b/src/lib/merchant_api_get_transfers.c
@@ -0,0 +1,351 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017, 2020 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_get_transfers.c
+ * @brief Implementation of the GET /transfers 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>
+
+
+/**
+ * @brief A Handle for tracking wire transfers.
+ */
+struct TALER_MERCHANT_GetTransfersHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_GetTransfersCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /transfers request.
+ *
+ * @param cls the `struct TALER_MERCHANT_GetTransfersHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_transfers_get_finished (void *cls,
+                               long response_code,
+                               const void *response)
+{
+  struct TALER_MERCHANT_GetTransfersHandle *gth = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  gth->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      json_t *transfers;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_json ("transfers",
+                               &transfers),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        break;
+      }
+      else
+      {
+        size_t tds_length;
+        struct TALER_MERCHANT_TransferData *tds;
+        json_t *transfer;
+        unsigned int i;
+        bool ok;
+
+        if (! json_is_array (transfers))
+        {
+          GNUNET_break_op (0);
+          GNUNET_JSON_parse_free (spec);
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+          break;
+        }
+        tds_length = json_array_size (transfers);
+        tds = GNUNET_new_array (tds_length,
+                                struct TALER_MERCHANT_TransferData);
+        ok = true;
+        json_array_foreach (transfers, i, transfer) {
+          /* FIXME: handle 'execution_time', 'verified', and/or 'confirmed'
+                    not present in the response. */
+          struct TALER_MERCHANT_TransferData *td = &tds[i];
+          struct GNUNET_JSON_Specification ispec[] = {
+            TALER_JSON_spec_amount ("credit_amount",
+                                    &td->credit_amount),
+            GNUNET_JSON_spec_fixed_auto ("wtid",
+                                         &td->wtid),
+            GNUNET_JSON_spec_string ("payto_uri",
+                                     &td->payto_uri),
+            GNUNET_JSON_spec_string ("exchange_url",
+                                     &td->exchange_url),
+            GNUNET_JSON_spec_uint64 ("transfer_serial_id",
+                                     &td->credit_serial),
+            GNUNET_JSON_spec_absolute_time ("execution_time",
+                                            &td->execution_time),
+            GNUNET_JSON_spec_bool ("verified",
+                                   &td->verified),
+            GNUNET_JSON_spec_bool ("confirmed",
+                                   &td->confirmed),
+            GNUNET_JSON_spec_end ()
+          };
+
+          if (GNUNET_OK !=
+              GNUNET_JSON_parse (transfer,
+                                 ispec,
+                                 NULL, NULL))
+          {
+            GNUNET_break_op (0);
+            ok = false;
+            break;
+          }
+        }
+
+        if (! ok)
+        {
+          GNUNET_break_op (0);
+          GNUNET_free (tds);
+          GNUNET_JSON_parse_free (spec);
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+          break;
+        }
+        gth->cb (gth->cb_cls,
+                 &hr,
+                 tds_length,
+                 tds);
+        GNUNET_free (tds);
+        GNUNET_JSON_parse_free (spec);
+        TALER_MERCHANT_transfers_get_cancel (gth);
+        return;
+      }
+    }
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    response_code = 0;
+    break;
+  }
+  gth->cb (gth->cb_cls,
+           &hr,
+           0,
+           NULL);
+  TALER_MERCHANT_transfers_get_cancel (gth);
+}
+
+
+/**
+ * Request backend to return list of all wire transfers that
+ * we received (or that the exchange claims we should have received).
+ *
+ * Note that when filtering by timestamp (using “before” and/or “after”), we
+ * use the time reported by the exchange and thus will ONLY return results for
+ * which we already have a response from the exchange. This should be
+ * virtually all transfers, however it is conceivable that for some transfer
+ * the exchange responded with a temporary error (i.e. HTTP status 500+) and
+ * then we do not yet have an execution time to filter by. Thus, IF timestamp
+ * filters are given, transfers for which we have no response from the
+ * exchange yet are automatically excluded.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the backend
+ * @param payto_uri filter by this credit account of the merchant
+ * @param before limit to transactions before this timestamp, use
+ *                 #GNUNET_TIME_UNIT_FOREVER_ABS to not filter by @a before
+ * @param after limit to transactions after this timestamp, use
+ *                 #GNUNET_TIME_UNIT_ZERO_ABS to not filter by @a after
+ * @param limit return at most ths number of results; negative to descend in 
execution time
+ * @param offset start at this "credit serial" number (exclusive)
+ * @param verified filter results by verification status
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_GetTransfersHandle *
+TALER_MERCHANT_transfers_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *payto_uri,
+  const struct GNUNET_TIME_Absolute before,
+  const struct GNUNET_TIME_Absolute after,
+  int64_t limit,
+  uint64_t offset,
+  enum TALER_EXCHANGE_YesNoAll verified,
+  TALER_MERCHANT_GetTransfersCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_GetTransfersHandle *gth;
+  CURL *eh;
+  const char *verified_s = NULL;
+  char limit_s[30];
+  char offset_s[30];
+  char *before_s;
+  char *after_s;
+
+  gth = GNUNET_new (struct TALER_MERCHANT_GetTransfersHandle);
+  gth->ctx = ctx;
+  gth->cb = cb;
+  gth->cb_cls = cb_cls;
+  verified_s = TALER_yna_to_string (verified);
+  GNUNET_snprintf (limit_s,
+                   sizeof (limit_s),
+                   "%lld",
+                   (long long) limit);
+  GNUNET_snprintf (offset_s,
+                   sizeof (offset_s),
+                   "%lld",
+                   (unsigned long long) offset);
+  before_s = GNUNET_strdup (GNUNET_STRINGS_absolute_time_to_string (before));
+  after_s = GNUNET_strdup (GNUNET_STRINGS_absolute_time_to_string (after));
+  gth->url = TALER_url_join (backend_url,
+                             "private/transfers",
+                             "payto_uri",
+                             payto_uri,
+                             "verified",
+                             (TALER_EXCHANGE_YNA_ALL != verified)
+                             ? verified_s
+                             : NULL,
+                             "limit",
+                             0 != limit
+                             ? limit_s
+                             : NULL,
+                             "offset",
+                             ((0 != offset) && (UINT64_MAX != offset))
+                             ? offset_s
+                             : NULL,
+                             "before",
+                             before.abs_value_us !=
+                             GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us
+                             ? before_s
+                             : NULL,
+                             "after",
+                             after.abs_value_us != 0
+                             ? after_s
+                             : NULL,
+                             NULL);
+  GNUNET_free (before_s);
+  GNUNET_free (after_s);
+  if (NULL == gth->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (gth);
+    return NULL;
+  }
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   gth->url));
+  gth->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_transfers_get_finished,
+                                  gth);
+  return gth;
+}
+
+
+/**
+ * Cancel a GET /transfers request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param gth handle to the tracking operation being cancelled
+ */
+void
+TALER_MERCHANT_transfers_get_cancel (
+  struct TALER_MERCHANT_GetTransfersHandle *gth)
+{
+  if (NULL != gth->job)
+  {
+    GNUNET_CURL_job_cancel (gth->job);
+    gth->job = NULL;
+  }
+  GNUNET_free (gth->url);
+  GNUNET_free (gth);
+}
+
+
+/* end of merchant_api_get_transfers.c */
diff --git a/src/lib/merchant_api_history.c b/src/lib/merchant_api_history.c
deleted file mode 100644
index 2bf49ef..0000000
--- a/src/lib/merchant_api_history.c
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014, 2015, 2016, 2020 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_history.c
- * @brief Implementation of the /history request of the merchant's
- *        HTTP API
- * @author Marcello Stanisci
- */
-#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>
-
-
-/**
- * @brief A Contract Operation Handle
- */
-struct TALER_MERCHANT_HistoryOperation
-{
-
-  /**
-   * The url for this request, including parameters.
-   */
-  char *url;
-
-  /**
-   * Handle for the request.
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the result.
-   */
-  TALER_MERCHANT_HistoryOperationCallback cb;
-
-  /**
-   * Closure for @a cb.
-   */
-  void *cb_cls;
-
-  /**
-   * Reference to the execution context.
-   */
-  struct GNUNET_CURL_Context *ctx;
-};
-
-
-/**
- * Cancel a pending /history request
- *
- * @param handle from the operation to cancel
- */
-void
-TALER_MERCHANT_history_cancel (struct TALER_MERCHANT_HistoryOperation *ho)
-{
-  if (NULL != ho->job)
-  {
-    GNUNET_CURL_job_cancel (ho->job);
-    ho->job = NULL;
-  }
-  GNUNET_free (ho->url);
-  GNUNET_free (ho);
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /history request.
- *
- * @param cls the `struct TALER_MERCHANT_TrackTransactionHandle`
- * @param response_code HTTP response code, 0 on error
- * @param json response body, NULL if not in JSON
- */
-static void
-history_raw_cb (void *cls,
-                long response_code,
-                const void *response)
-{
-  struct TALER_MERCHANT_HistoryOperation *ho = cls;
-  const json_t *json = response;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
-  };
-
-  ho->job = NULL;
-  switch (response_code)
-  {
-  case 0:
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "/history returned response code 0\n");
-    /**
-     * The response was malformed or didn't have the
-     * application/json header.
-     */
-    hr.ec = TALER_EC_INVALID_RESPONSE;
-    break;
-  case MHD_HTTP_OK:
-    /* all good already */
-    break;
-  case MHD_HTTP_INTERNAL_SERVER_ERROR:
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "/history URL not found\n");
-    break;
-  case MHD_HTTP_BAD_REQUEST:
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Wrong/missing URL parameter\n");
-    break;
-  default:
-    /* unexpected response code */
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d\n",
-                (unsigned int) response_code,
-                (int) hr.ec);
-    GNUNET_break_op (0);
-    break;
-  }
-  ho->cb (ho->cb_cls,
-          &hr);
-  TALER_MERCHANT_history_cancel (ho);
-}
-
-
-/**
- * Issue a /history request to the backend.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param start return `delta` records starting from position `start`.
- *        If given as zero, then no initial skip of `start` records is done.
- * @param use_default_start do NOT include the 'start' argument in URL.
- * @param delta return `delta` records starting from position `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 @a history_cb
- * @return handle for this operation, NULL upon errors
- */
-static struct TALER_MERCHANT_HistoryOperation *
-TALER_MERCHANT_history2 (struct GNUNET_CURL_Context *ctx,
-                         const char *backend_url,
-                         unsigned long long start,
-                         int use_default_start,
-                         long long delta,
-                         struct GNUNET_TIME_Absolute date,
-                         TALER_MERCHANT_HistoryOperationCallback history_cb,
-                         void *history_cb_cls)
-{
-  struct TALER_MERCHANT_HistoryOperation *ho;
-  uint64_t seconds;
-  CURL *eh;
-  char *base;
-
-  base = TALER_url_join (backend_url, "history", NULL);
-  if (NULL == base)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not construct request URL.\n");
-    return NULL;
-  }
-  ho = GNUNET_new (struct TALER_MERCHANT_HistoryOperation);
-  ho->ctx = ctx;
-  ho->cb = history_cb;
-  ho->cb_cls = history_cb_cls;
-  seconds = date.abs_value_us / 1000LL / 1000LL;
-
-  if (GNUNET_YES == use_default_start)
-    GNUNET_asprintf (&ho->url,
-                     "%s?date=%llu&delta=%lld",
-                     base,
-                     seconds,
-                     delta);
-  else
-    GNUNET_asprintf (&ho->url,
-                     "%s?date=%llu&delta=%lld&start=%llu",
-                     base,
-                     seconds,
-                     delta,
-                     start);
-  GNUNET_free (base);
-  eh = curl_easy_init ();
-  if (CURLE_OK != curl_easy_setopt (eh,
-                                    CURLOPT_URL,
-                                    ho->url))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-
-  if (NULL == (ho->job = GNUNET_CURL_job_add (ctx,
-                                              eh,
-                                              GNUNET_YES,
-                                              &history_raw_cb,
-                                              ho)))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  return ho;
-}
-
-
-/**
- * Issue a /history request to the backend.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param start return `delta` records starting from position
- *        `start`.  If given as zero, then no initial skip of
- *        `start` records is done.
- * @param delta return `delta` records starting from position
- *        `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 @a history_cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_HistoryOperation *
-TALER_MERCHANT_history_default_start (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  long long delta,
-  struct GNUNET_TIME_Absolute date,
-  TALER_MERCHANT_HistoryOperationCallback history_cb,
-  void *history_cb_cls)
-{
-  return TALER_MERCHANT_history2 (ctx,
-                                  backend_url,
-                                  /* fake 'start' argument: will NOT be used */
-                                  -1,
-                                  /* Specifies "no start argument" in final 
URL */
-                                  GNUNET_YES,
-                                  delta,
-                                  date,
-                                  history_cb,
-                                  history_cb_cls);
-}
-
-
-/**
- * Issue a /history request to the backend.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param start return `delta` records starting from position
- *        `start`.  If given as zero, then no initial skip of
- *        `start` records is done.
- * @param delta return `delta` records starting from position
- *        `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 @a history_cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_HistoryOperation *
-TALER_MERCHANT_history (struct GNUNET_CURL_Context *ctx,
-                        const char *backend_url,
-                        unsigned long long start,
-                        long long delta,
-                        struct GNUNET_TIME_Absolute date,
-                        TALER_MERCHANT_HistoryOperationCallback history_cb,
-                        void *history_cb_cls)
-{
-  return TALER_MERCHANT_history2 (ctx,
-                                  backend_url,
-                                  start,
-                                  GNUNET_NO,
-                                  delta,
-                                  date,
-                                  history_cb,
-                                  history_cb_cls);
-}
-
-
-/* end of merchant_api_history.c */
diff --git a/src/lib/merchant_api_lock_product.c 
b/src/lib/merchant_api_lock_product.c
new file mode 100644
index 0000000..3b91d47
--- /dev/null
+++ b/src/lib/merchant_api_lock_product.c
@@ -0,0 +1,265 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_lock_product.c
+ * @brief Implementation of the POST /products/$ID/lock request
+ *        of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /products/$ID/lock operation.
+ */
+struct TALER_MERCHANT_ProductLockHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ProductLockCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /products/$ID/lock request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ProductLockHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_lock_product_finished (void *cls,
+                              long response_code,
+                              const void *response)
+{
+  struct TALER_MERCHANT_ProductLockHandle *plh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  plh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "LOCK /products/$ID completed with response code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_break_op (0);
+    /* This should never happen, either us
+     * or the merchant is buggy (or API version conflict);
+     * just pass JSON reply to the application */
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, merchant says we tried to abort the payment
+     * after it was successful. We should pass the JSON reply to the
+     * application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_GONE:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Server had an internal issue; we should retry,
+       but this API leaves this to the application */
+    break;
+  default:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  plh->cb (plh->cb_cls,
+           &hr);
+  TALER_MERCHANT_product_lock_cancel (plh);
+}
+
+
+/**
+ * Make a POST /products/$ID/lock request to reserve a certain
+ * amount of product in inventory to a reservation UUID.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier of the product
+ * @param uuid UUID that identifies the client holding the lock
+ * @param duration how long should the lock be held
+ * @param quantity how much product should be locked
+ * @param cb function to call with the backend's lock status
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductLockHandle *
+TALER_MERCHANT_product_lock (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  const struct GNUNET_Uuid *uuid,
+  struct GNUNET_TIME_Relative duration,
+  uint32_t quantity,
+  TALER_MERCHANT_ProductLockCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_ProductLockHandle *plh;
+  json_t *req_obj;
+
+  req_obj = json_pack ("{s:o, s:o, s:I}",
+                       "lock_uuid",
+                       GNUNET_JSON_from_data_auto (uuid),
+                       "duration",
+                       GNUNET_JSON_from_time_rel (duration),
+                       "quantity",
+                       (json_int_t) quantity);
+  if (NULL == req_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  plh = GNUNET_new (struct TALER_MERCHANT_ProductLockHandle);
+  plh->ctx = ctx;
+  plh->cb = cb;
+  plh->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/products/%s/lock",
+                     product_id);
+    plh->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == plh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    json_decref (req_obj);
+    GNUNET_free (plh);
+    return NULL;
+  }
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&plh->post_ctx,
+                              eh,
+                              req_obj))
+    {
+      GNUNET_break (0);
+      json_decref (req_obj);
+      GNUNET_free (plh);
+      return NULL;
+    }
+
+    json_decref (req_obj);
+    GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
+                                                 CURLOPT_URL,
+                                                 plh->url));
+    plh->job = GNUNET_CURL_job_add2 (ctx,
+                                     eh,
+                                     plh->post_ctx.headers,
+                                     &handle_lock_product_finished,
+                                     plh);
+  }
+  return plh;
+}
+
+
+/**
+ * Cancel POST /products/$ID/lock request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param plh request to cancel.
+ */
+void
+TALER_MERCHANT_product_lock_cancel (
+  struct TALER_MERCHANT_ProductLockHandle *plh)
+{
+  if (NULL != plh->job)
+  {
+    GNUNET_CURL_job_cancel (plh->job);
+    plh->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&plh->post_ctx);
+  GNUNET_free (plh->url);
+  GNUNET_free (plh);
+}
+
+
+/* end of merchant_api_lock_product.c */
diff --git a/src/lib/merchant_api_merchant_get_order.c 
b/src/lib/merchant_api_merchant_get_order.c
new file mode 100644
index 0000000..edd00f7
--- /dev/null
+++ b/src/lib/merchant_api_merchant_get_order.c
@@ -0,0 +1,515 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018, 2019, 2020 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_merchant_get_order.c
+ * @brief Implementation of the GET /private/orders/$ORDER request
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ * @author Florian Dold
+ */
+#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>
+
+
+/**
+ * @brief A GET /private/orders/$ORDER handle
+ */
+struct TALER_MERCHANT_OrderMerchantGetHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_OrderMerchantGetCallback 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 GET /private/orders/$ORDER
+ * request and we got an HTTP status of OK and the order was unpaid. Parse
+ * the response and call the callback.
+ *
+ * @param omgh handle for the request
+ * @param[in,out] hr HTTP response we got
+ */
+static void
+handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
+               struct TALER_MERCHANT_HttpResponse *hr)
+{
+  const char *taler_pay_uri
+    = json_string_value (json_object_get (hr->reply,
+                                          "taler_pay_uri"));
+  const char *already_paid_order_id
+    = json_string_value (json_object_get (hr->reply,
+                                          "already_paid_order_id"));
+  if (NULL == taler_pay_uri)
+  {
+    GNUNET_break_op (0);
+    hr->http_status = 0;
+    hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
+    omgh->cb (omgh->cb_cls,
+              hr,
+              NULL);
+    return;
+  }
+  {
+    struct TALER_MERCHANT_OrderStatusResponse osr = {
+      .paid = false,
+      .details.unpaid.taler_pay_uri = taler_pay_uri,
+      .details.unpaid.already_paid_order_id = already_paid_order_id
+    };
+
+    omgh->cb (omgh->cb_cls,
+              hr,
+              &osr);
+  }
+}
+
+
+/**
+ * Function called when we're done processing the GET /private/orders/$ORDER
+ * request and we got an HTTP status of OK and the order was paid. Parse
+ * the response and call the callback.
+ *
+ * @param omgh handle for the request
+ * @param[in,out] hr HTTP response we got
+ */
+static void
+handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
+             struct TALER_MERCHANT_HttpResponse *hr)
+{
+  uint32_t ec32;
+  uint32_t hc32;
+  json_t *wire_details;
+  json_t *wire_reports;
+  json_t *refund_details;
+  struct TALER_MERCHANT_OrderStatusResponse osr = {
+    .paid = true
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_bool ("refunded",
+                           &osr.details.paid.refunded),
+    GNUNET_JSON_spec_bool ("wired",
+                           &osr.details.paid.wired),
+    TALER_JSON_spec_amount ("deposit_total",
+                            &osr.details.paid.deposit_total),
+    GNUNET_JSON_spec_uint32 ("exchange_ec",
+                             &ec32),
+    GNUNET_JSON_spec_uint32 ("exchange_hc",
+                             &hc32),
+    TALER_JSON_spec_amount ("refund_amount",
+                            &osr.details.paid.refund_amount),
+    GNUNET_JSON_spec_json ("contract_terms",
+                           (json_t **) &osr.details.paid.contract_terms),
+    GNUNET_JSON_spec_json ("wire_details",
+                           &wire_details),
+    GNUNET_JSON_spec_json ("wire_reports",
+                           &wire_reports),
+    GNUNET_JSON_spec_json ("refund_details",
+                           &refund_details),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (hr->reply,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    hr->http_status = 0;
+    hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
+    omgh->cb (omgh->cb_cls,
+              hr,
+              NULL);
+    return;
+  }
+  if (! (json_is_array (wire_details) &&
+         json_is_array (wire_reports) &&
+         json_is_array (refund_details) &&
+         json_is_object (osr.details.paid.contract_terms)) )
+  {
+    GNUNET_break_op (0);
+    hr->http_status = 0;
+    hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
+    omgh->cb (omgh->cb_cls,
+              hr,
+              NULL);
+    GNUNET_JSON_parse_free (spec);
+    return;
+  }
+  osr.details.paid.exchange_ec = (enum TALER_ErrorCode) ec32;
+  osr.details.paid.exchange_hc = (unsigned int) hc32;
+  {
+    unsigned int wts_len = json_array_size (wire_details);
+    unsigned int wrs_len = json_array_size (wire_reports);
+    unsigned int ref_len = json_array_size (refund_details);
+    struct TALER_MERCHANT_WireTransfer wts[wts_len];
+    struct TALER_MERCHANT_WireReport wrs[wrs_len];
+    struct TALER_MERCHANT_RefundOrderDetail ref[ref_len];
+
+    for (unsigned int i = 0; i<wts_len; i++)
+    {
+      struct TALER_MERCHANT_WireTransfer *wt = &wts[i];
+      const json_t *w = json_array_get (wire_details,
+                                        i);
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_string ("exchange_url",
+                                 &wt->exchange_url),
+        GNUNET_JSON_spec_fixed_auto ("wtid",
+                                     &wt->wtid),
+        GNUNET_JSON_spec_absolute_time ("execution_time",
+                                        &wt->execution_time),
+        TALER_JSON_spec_amount ("amount",
+                                &wt->total_amount),
+        GNUNET_JSON_spec_bool ("confirmed",
+                               &wt->confirmed),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (w,
+                             ispec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr->http_status = 0;
+        hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
+        omgh->cb (omgh->cb_cls,
+                  hr,
+                  NULL);
+        GNUNET_JSON_parse_free (spec);
+        return;
+      }
+    }
+
+    for (unsigned int i = 0; i<wrs_len; i++)
+    {
+      struct TALER_MERCHANT_WireReport *wr = &wrs[i];
+      const json_t *w = json_array_get (wire_reports, i);
+      uint32_t c32;
+      uint32_t eec32;
+      uint32_t ehs32;
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_uint32 ("code",
+                                 &c32),
+        GNUNET_JSON_spec_uint32 ("exchange_ec",
+                                 &eec32),
+        GNUNET_JSON_spec_uint32 ("exchange_hc",
+                                 &ehs32),
+        GNUNET_JSON_spec_string ("hint",
+                                 &wr->hint),
+        GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                     &wr->coin_pub),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (w,
+                             ispec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr->http_status = 0;
+        hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
+        omgh->cb (omgh->cb_cls,
+                  hr,
+                  NULL);
+        GNUNET_JSON_parse_free (spec);
+        return;
+      }
+      wr->code = (enum TALER_ErrorCode) c32;
+      wr->hr.ec = (enum TALER_ErrorCode) eec32;
+      wr->hr.http_status = (unsigned int) ehs32;
+    }
+
+    for (unsigned int i = 0; i<ref_len; i++)
+    {
+      struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i];
+      const json_t *w = json_array_get (refund_details,
+                                        i);
+      struct GNUNET_JSON_Specification ispec[] = {
+        TALER_JSON_spec_amount ("amount",
+                                &ro->refund_amount),
+        GNUNET_JSON_spec_string ("reason",
+                                 &ro->reason),
+        GNUNET_JSON_spec_absolute_time ("timestamp",
+                                        &ro->refund_time),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (w,
+                             ispec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr->http_status = 0;
+        hr->ec = TALER_EC_MERCHANT_ORDER_GET_REPLY_MALFORMED;
+        omgh->cb (omgh->cb_cls,
+                  hr,
+                  NULL);
+        GNUNET_JSON_parse_free (spec);
+        return;
+      }
+    }
+
+    osr.details.paid.wts = wts;
+    osr.details.paid.wts_len = wts_len;
+    osr.details.paid.wrs = wrs;
+    osr.details.paid.wrs_len = wrs_len;
+    osr.details.paid.refunds = ref;
+    osr.details.paid.refunds_len = ref_len;
+    omgh->cb (omgh->cb_cls,
+              hr,
+              &osr);
+  }
+  GNUNET_JSON_parse_free (spec);
+}
+
+
+/**
+ * Function called when we're done processing the GET /private/orders/$ORDER
+ * request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OrderMerchantGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, should be NULL
+ */
+static void
+handle_merchant_order_get_finished (void *cls,
+                                    long response_code,
+                                    const void *response)
+{
+  struct TALER_MERCHANT_OrderMerchantGetHandle *omgh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  omgh->job = NULL;
+  switch (response_code)
+  {
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    omgh->cb (omgh->cb_cls,
+              &hr,
+              NULL);
+    TALER_MERCHANT_merchant_order_get_cancel (omgh);
+    return;
+  case MHD_HTTP_OK:
+    /* see below */
+    break;
+  default:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Polling payment failed with HTTP status code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    omgh->cb (omgh->cb_cls,
+              &hr,
+              NULL);
+    TALER_MERCHANT_merchant_order_get_cancel (omgh);
+    return;
+  }
+
+  /* HTTP OK */
+  if (! json_boolean_value (json_object_get (json, "paid")))
+    handle_unpaid (omgh,
+                   &hr);
+  else
+    handle_paid (omgh,
+                 &hr);
+}
+
+
+/**
+ * Checks the status of a payment.  Issue a GET /private/orders/$ID request to
+ * the backend.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param order_id order id to identify the payment
+ * @param session_id sesion id for the payment (or NULL if the check is not
+ *                   bound to a session)
+ * @param transfer if true, obtain the wire transfer status from the exhcange.
+ *               Otherwise, the wire transfer status MAY be returned if it is 
available.
+ * @param timeout timeout to use in long polling (how long may the server wait 
to reply
+ *        before generating an unpaid response). Note that this is just 
provided to
+ *        the server, we as client will block until the response comes back or 
until
+ *        #TALER_MERCHANT_order_get_cancel() is called.
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_OrderMerchantGetHandle *
+TALER_MERCHANT_merchant_order_get (struct GNUNET_CURL_Context *ctx,
+                                   const char *backend_url,
+                                   const char *order_id,
+                                   const char *session_id,
+                                   bool transfer,
+                                   struct GNUNET_TIME_Relative timeout,
+                                   TALER_MERCHANT_OrderMerchantGetCallback cb,
+                                   void *cb_cls)
+{
+  struct TALER_MERCHANT_OrderMerchantGetHandle *omgh;
+  unsigned long long tms;
+  long tlong;
+
+  GNUNET_assert (NULL != backend_url);
+  GNUNET_assert (NULL != order_id);
+  tms = (unsigned long long) (timeout.rel_value_us
+                              / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+  /* set curl timeout to *our* long poll timeout plus one minute
+     (for network latency and processing delays) */
+  tlong = (long) (GNUNET_TIME_relative_add (timeout,
+                                            GNUNET_TIME_UNIT_MINUTES).
+                  rel_value_us
+                  / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+  omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle);
+  omgh->ctx = ctx;
+  omgh->cb = cb;
+  omgh->cb_cls = cb_cls;
+  {
+    char *path;
+    char timeout_ms[32];
+
+    GNUNET_snprintf (timeout_ms,
+                     sizeof (timeout_ms),
+                     "%llu",
+                     tms);
+    GNUNET_asprintf (&path,
+                     "private/orders/%s",
+                     order_id);
+    omgh->url = TALER_url_join (backend_url,
+                                path,
+                                "session_id", session_id,
+                                "transfer", transfer ? "YES" : "NO",
+                                (0 != tms) ? "timeout_ms" : NULL,
+                                timeout_ms,
+                                NULL);
+  }
+  if (NULL == omgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (omgh);
+    return NULL;
+  }
+
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    if (NULL == eh)
+    {
+      GNUNET_break (0);
+      GNUNET_free (omgh->url);
+      GNUNET_free (omgh);
+      return NULL;
+    }
+    if (CURLE_OK != curl_easy_setopt (eh,
+                                      CURLOPT_URL,
+                                      omgh->url))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      GNUNET_free (omgh->url);
+      GNUNET_free (omgh);
+      return NULL;
+    }
+    if (CURLE_OK != curl_easy_setopt (eh,
+                                      CURLOPT_TIMEOUT_MS,
+                                      tlong))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      GNUNET_free (omgh->url);
+      GNUNET_free (omgh);
+      return NULL;
+    }
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Getting order status from %s\n",
+                omgh->url);
+    if (NULL == (omgh->job =
+                   GNUNET_CURL_job_add (ctx,
+                                        eh,
+                                        GNUNET_YES,
+                                        &handle_merchant_order_get_finished,
+                                        omgh)))
+    {
+      GNUNET_break (0);
+      GNUNET_free (omgh->url);
+      GNUNET_free (omgh);
+      return NULL;
+    }
+  }
+  return omgh;
+}
+
+
+/**
+ * Cancel a GET /private/orders/$ORDER request.
+ *
+ * @param omgh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_merchant_order_get_cancel (
+  struct TALER_MERCHANT_OrderMerchantGetHandle *omgh)
+{
+  if (NULL != omgh->job)
+  {
+    GNUNET_CURL_job_cancel (omgh->job);
+    omgh->job = NULL;
+  }
+  GNUNET_free (omgh->url);
+  GNUNET_free (omgh);
+}
+
+
+/* end of merchant_api_merchant_get_order.c */
diff --git a/src/lib/merchant_api_merchant_get_tip.c 
b/src/lib/merchant_api_merchant_get_tip.c
new file mode 100644
index 0000000..5ecd65e
--- /dev/null
+++ b/src/lib/merchant_api_merchant_get_tip.c
@@ -0,0 +1,339 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_merchant_get_tip.c
+ * @brief Implementation of the GET /private/tips/$TIP_ID request of the 
merchant's HTTP API
+ * @author Jonathan Buchanan
+ */
+#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>
+
+
+struct TALER_MERCHANT_TipMerchantGetHandle
+{
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_TipMerchantGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+};
+
+
+static int
+parse_pickups (const json_t *pa,
+               const struct TALER_Amount *total_authorized,
+               const struct TALER_Amount *total_picked_up,
+               const char *reason,
+               struct GNUNET_TIME_Absolute expiration,
+               const struct TALER_ReservePublicKeyP *reserve_pub,
+               struct TALER_MERCHANT_TipMerchantGetHandle *tgh)
+{
+  unsigned int pa_len = json_array_size (pa);
+  struct TALER_MERCHANT_PickupDetail pickups[pa_len];
+  size_t index;
+  json_t *value;
+  int ret = GNUNET_OK;
+
+  json_array_foreach (pa, index, value)
+  {
+    struct TALER_MERCHANT_PickupDetail *pickup = &pickups[index];
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("pickup_id",
+                                   &pickup->pickup_id),
+      GNUNET_JSON_spec_uint64 ("num_planchets",
+                               &pickup->num_planchets),
+      TALER_JSON_spec_amount ("requested_amount",
+                              &pickup->requested_amount),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           NULL,
+                           NULL))
+    {
+      GNUNET_break_op (0);
+      ret = GNUNET_SYSERR;
+      break;
+    }
+  }
+  if (GNUNET_OK == ret)
+  {
+    struct TALER_MERCHANT_HttpResponse hr = {
+      .http_status = MHD_HTTP_OK
+    };
+
+    tgh->cb (tgh->cb_cls,
+             &hr,
+             total_authorized,
+             total_picked_up,
+             reason,
+             expiration,
+             reserve_pub,
+             pa_len,
+             pickups);
+  }
+  return ret;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * GET /private/tips/$TIP_ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_TipMerchantGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_merchant_tip_get_finished (void *cls,
+                                  long response_code,
+                                  const void *response)
+{
+  struct TALER_MERCHANT_TipMerchantGetHandle *tgh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Got /private/tips/$TIP_ID response with status code %u\n",
+              (unsigned int) response_code);
+  tgh->job = NULL;
+  switch (response_code)
+  {
+  case MHD_HTTP_OK:
+    {
+      struct TALER_Amount total_authorized;
+      struct TALER_Amount total_picked_up;
+      const char *reason;
+      struct GNUNET_TIME_Absolute expiration;
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct GNUNET_JSON_Specification spec[] = {
+        TALER_JSON_spec_amount ("total_authorized",
+                                &total_authorized),
+        TALER_JSON_spec_amount ("total_picked_up",
+                                &total_picked_up),
+        GNUNET_JSON_spec_string ("reason",
+                                 &reason),
+        GNUNET_JSON_spec_absolute_time ("expiration",
+                                        &expiration),
+        GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                     &reserve_pub),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+      }
+      else
+      {
+        json_t *pickups = json_object_get (json,
+                                           "pickups");
+        if (! json_is_array (pickups))
+        {
+          tgh->cb (tgh->cb_cls,
+                   &hr,
+                   &total_authorized,
+                   &total_picked_up,
+                   reason,
+                   expiration,
+                   &reserve_pub,
+                   0,
+                   NULL);
+          TALER_MERCHANT_merchant_tip_get_cancel (tgh);
+          return;
+        }
+        else if (GNUNET_OK == parse_pickups (pickups,
+                                             &total_authorized,
+                                             &total_picked_up,
+                                             reason,
+                                             expiration,
+                                             &reserve_pub,
+                                             tgh))
+        {
+          GNUNET_JSON_parse_free (spec);
+          TALER_MERCHANT_merchant_tip_get_cancel (tgh);
+          return;
+        }
+        else
+        {
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+        }
+      }
+      GNUNET_JSON_parse_free (spec);
+      break;
+    }
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* legal, can happen if instance or tip reserve is unknown */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  tgh->cb (tgh->cb_cls,
+           &hr,
+           NULL,
+           NULL,
+           NULL,
+           GNUNET_TIME_UNIT_ZERO_ABS,
+           NULL,
+           0,
+           NULL);
+  TALER_MERCHANT_merchant_tip_get_cancel (tgh);
+}
+
+
+/**
+ * Issue a GET /private/tips/$TIP_ID (private variant) request to the backend.
+ * Returns information needed to pick up a tip.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param tip_id which tip should we query
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_TipMerchantGetHandle *
+TALER_MERCHANT_merchant_tip_get (struct GNUNET_CURL_Context *ctx,
+                                 const char *backend_url,
+                                 const struct GNUNET_HashCode *tip_id,
+                                 bool pickups,
+                                 TALER_MERCHANT_TipMerchantGetCallback cb,
+                                 void *cb_cls)
+{
+  struct TALER_MERCHANT_TipMerchantGetHandle *tgh;
+  CURL *eh;
+
+  GNUNET_assert (NULL != backend_url);
+  tgh = GNUNET_new (struct TALER_MERCHANT_TipMerchantGetHandle);
+  tgh->ctx = ctx;
+  tgh->cb = cb;
+  tgh->cb_cls = cb_cls;
+
+  {
+    char res_str[sizeof (*tip_id) * 2];
+    char arg_str[sizeof (res_str) + 48];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (tip_id,
+                                         sizeof (*tip_id),
+                                         res_str,
+                                         sizeof (res_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "private/tips/%s",
+                     res_str);
+    tgh->url = TALER_url_join (backend_url,
+                               arg_str,
+                               "pickups", pickups ? "yes" : NULL,
+                               NULL);
+  }
+  if (NULL == tgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (tgh);
+    return NULL;
+  }
+
+  eh = curl_easy_init ();
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   tgh->url));
+  tgh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_YES,
+                                  &handle_merchant_tip_get_finished,
+                                  tgh);
+  return tgh;
+}
+
+
+/**
+ * Cancel a GET /private/tips/$TIP_ID request.
+ *
+ * @param tgh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_merchant_tip_get_cancel (struct
+                                        TALER_MERCHANT_TipMerchantGetHandle 
*tgh)
+{
+  if (NULL != tgh->job)
+  {
+    GNUNET_CURL_job_cancel (tgh->job);
+    tgh->job = NULL;
+  }
+  GNUNET_free (tgh->url);
+  GNUNET_free (tgh);
+}
+
+
+/* end of merchant_api_merchant_get_tip.c */
diff --git a/src/lib/merchant_api_patch_instance.c 
b/src/lib/merchant_api_patch_instance.c
new file mode 100644
index 0000000..f410235
--- /dev/null
+++ b/src/lib/merchant_api_patch_instance.c
@@ -0,0 +1,315 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_patch_instance.c
+ * @brief Implementation of the PATCH /instances/$ID request
+ *        of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a PATCH /instances/$ID operation.
+ */
+struct TALER_MERCHANT_InstancePatchHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_InstancePatchCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP PATCH /instances/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_InstancePatchHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_patch_instance_finished (void *cls,
+                                long response_code,
+                                const void *response)
+{
+  struct TALER_MERCHANT_InstancePatchHandle *iph = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  iph->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "PATCH /instances/$ID completed with response code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_break_op (0);
+    /* This should never happen, either us
+     * or the merchant is buggy (or API version conflict);
+     * just pass JSON reply to the application */
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, merchant says we tried to abort the payment
+     * after it was successful. We should pass the JSON reply to the
+     * application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_CONFLICT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Server had an internal issue; we should retry,
+       but this API leaves this to the application */
+    break;
+  default:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  iph->cb (iph->cb_cls,
+           &hr);
+  TALER_MERCHANT_instance_patch_cancel (iph);
+}
+
+
+/**
+ * Update instance configuration.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id identity of the instance to get information about
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param cb function to call with the
+ *        backend's instances information
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstancePatchHandle *
+TALER_MERCHANT_instance_patch (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *instance_id,
+  unsigned int accounts_length,
+  const char *payto_uris[],
+  const char *name,
+  const json_t *address,
+  const json_t *jurisdiction,
+  const struct TALER_Amount *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const struct TALER_Amount *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  TALER_MERCHANT_InstancePatchCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_InstancePatchHandle *iph;
+  json_t *jpayto_uris;
+  json_t *req_obj;
+
+  jpayto_uris = json_array ();
+  if (NULL == jpayto_uris)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  for (unsigned int i = 0; i<accounts_length; i++)
+  {
+    if (0 !=
+        json_array_append_new (jpayto_uris,
+                               json_string (payto_uris[i])))
+    {
+      GNUNET_break (0);
+      json_decref (jpayto_uris);
+      return NULL;
+    }
+  }
+  req_obj = json_pack ("{s:o, s:s, s:O, s:O"
+                       " s:o, s:I: s:o, s:o, s:o}",
+                       "payto_uris",
+                       jpayto_uris,
+                       "name",
+                       name,
+                       "address",
+                       address,
+                       "jurisdiction",
+                       jurisdiction,
+                       /* end of group of 4 */
+                       "default_max_wire_fee",
+                       TALER_JSON_from_amount (default_max_wire_fee),
+                       "default_wire_fee_amortization",
+                       (json_int_t) default_wire_fee_amortization,
+                       "default_max_deposit_fee",
+                       TALER_JSON_from_amount (default_max_deposit_fee),
+                       "default_wire_transfer_delay",
+                       GNUNET_JSON_from_time_rel (default_wire_transfer_delay),
+                       "default_pay_delay",
+                       GNUNET_JSON_from_time_rel (default_pay_delay));
+  if (NULL == req_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  iph = GNUNET_new (struct TALER_MERCHANT_InstancePatchHandle);
+  iph->ctx = ctx;
+  iph->cb = cb;
+  iph->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/instances/%s",
+                     instance_id);
+    iph->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == iph->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    json_decref (req_obj);
+    GNUNET_free (iph);
+    return NULL;
+  }
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&iph->post_ctx,
+                              eh,
+                              req_obj))
+    {
+      GNUNET_break (0);
+      json_decref (req_obj);
+      GNUNET_free (iph);
+      return NULL;
+    }
+
+    json_decref (req_obj);
+    GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
+                                                 CURLOPT_URL,
+                                                 iph->url));
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_CUSTOMREQUEST,
+                                     MHD_HTTP_METHOD_PATCH));
+    iph->job = GNUNET_CURL_job_add2 (ctx,
+                                     eh,
+                                     iph->post_ctx.headers,
+                                     &handle_patch_instance_finished,
+                                     iph);
+  }
+  return iph;
+}
+
+
+/**
+ * Cancel PATCH /instances/$ID request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param iph request to cancel.
+ */
+void
+TALER_MERCHANT_instance_patch_cancel (
+  struct TALER_MERCHANT_InstancePatchHandle *iph)
+{
+  if (NULL != iph->job)
+  {
+    GNUNET_CURL_job_cancel (iph->job);
+    iph->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&iph->post_ctx);
+  GNUNET_free (iph->url);
+  GNUNET_free (iph);
+}
+
+
+/* end of merchant_api_patch_instance.c */
diff --git a/src/lib/merchant_api_patch_product.c 
b/src/lib/merchant_api_patch_product.c
new file mode 100644
index 0000000..c97bd1a
--- /dev/null
+++ b/src/lib/merchant_api_patch_product.c
@@ -0,0 +1,308 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_patch_product.c
+ * @brief Implementation of the PATCH /products/$ID request
+ *        of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a PATCH /products/$ID operation.
+ */
+struct TALER_MERCHANT_ProductPatchHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ProductPatchCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP PATCH /products/$ID request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ProductPatchHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_patch_product_finished (void *cls,
+                               long response_code,
+                               const void *response)
+{
+  struct TALER_MERCHANT_ProductPatchHandle *pph = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  pph->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "PATCH /products/$ID completed with response code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    GNUNET_break_op (0);
+    /* This should never happen, either us
+     * or the merchant is buggy (or API version conflict);
+     * just pass JSON reply to the application */
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, merchant says we tried to abort the payment
+     * after it was successful. We should pass the JSON reply to the
+     * application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_CONFLICT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Server had an internal issue; we should retry,
+       but this API leaves this to the application */
+    break;
+  default:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  pph->cb (pph->cb_cls,
+           &hr);
+  TALER_MERCHANT_product_patch_cancel (pph);
+}
+
+
+/**
+ * Make a PATCH /products request to update product details in the
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier to use for the product; the product must exist,
+ *                    or the transaction will fail with a #MHD_HTTP_NOT_FOUND
+ *                    HTTP status code
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books),
+ *               must be larger than previous values
+ * @param total_lost in @a units, must be larger than previous values, and may
+ *               not exceed total_stock minus total_sold; if it does, the 
transaction
+ *               will fail with a #MHD_HTTP_CONFLICT HTTP status code
+ * @param location where the product is in stock
+ * @param next_restock when the next restocking is expected to happen
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductPatchHandle *
+TALER_MERCHANT_product_patch (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  const char *description,
+  const json_t *description_i18n,
+  const char *unit,
+  const struct TALER_Amount *price,
+  const json_t *image,
+  const json_t *taxes,
+  int64_t total_stock,
+  uint64_t total_lost,
+  const json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  TALER_MERCHANT_ProductPatchCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_ProductPatchHandle *pph;
+  json_t *req_obj;
+
+  (void) GNUNET_TIME_round_abs (&next_restock);
+  req_obj = json_pack ("{s:s, s:O, s:s, s:o, s:O,"
+                       " s:O, s:I: s:I, s:O, s:o}",
+                       "description",
+                       description,
+                       "description_i18n",
+                       description_i18n,
+                       "unit",
+                       unit,
+                       "price",
+                       TALER_JSON_from_amount (price),
+                       "image",
+                       image,
+                       /* End of first group of 5 */
+                       "taxes",
+                       taxes,
+                       "total_stock",
+                       (json_int_t) total_stock,
+                       "total_lost",
+                       (json_int_t) total_lost,
+                       "address",
+                       address,
+                       "next_restock",
+                       GNUNET_JSON_from_time_abs (next_restock));
+  if (NULL == req_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  pph = GNUNET_new (struct TALER_MERCHANT_ProductPatchHandle);
+  pph->ctx = ctx;
+  pph->cb = cb;
+  pph->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/products/%s",
+                     product_id);
+    pph->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == pph->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    json_decref (req_obj);
+    GNUNET_free (pph);
+    return NULL;
+  }
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&pph->post_ctx,
+                              eh,
+                              req_obj))
+    {
+      GNUNET_break (0);
+      json_decref (req_obj);
+      GNUNET_free (pph);
+      return NULL;
+    }
+
+    json_decref (req_obj);
+    GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
+                                                 CURLOPT_URL,
+                                                 pph->url));
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_CUSTOMREQUEST,
+                                     MHD_HTTP_METHOD_PATCH));
+    pph->job = GNUNET_CURL_job_add2 (ctx,
+                                     eh,
+                                     pph->post_ctx.headers,
+                                     &handle_patch_product_finished,
+                                     pph);
+  }
+  return pph;
+}
+
+
+/**
+ * Cancel PATCH /products/$ID request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param pph request to cancel.
+ */
+void
+TALER_MERCHANT_product_patch_cancel (
+  struct TALER_MERCHANT_ProductPatchHandle *pph)
+{
+  if (NULL != pph->job)
+  {
+    GNUNET_CURL_job_cancel (pph->job);
+    pph->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&pph->post_ctx);
+  GNUNET_free (pph->url);
+  GNUNET_free (pph);
+}
+
+
+/* end of merchant_api_patch_product.c */
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
deleted file mode 100644
index 2bbc19c..0000000
--- a/src/lib/merchant_api_pay.c
+++ /dev/null
@@ -1,1067 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014, 2015, 2016, 2017, 2020 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_pay.c
- * @brief Implementation of the /pay request
- *        of the merchant's HTTP API
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#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 <taler/taler_exchange_service.h>
-#include <taler/taler_curl_lib.h>
-
-
-/**
- * @brief A Pay Handle
- */
-struct TALER_MERCHANT_Pay
-{
-
-  /**
-   * The url for this request.
-   */
-  char *url;
-
-  /**
-   * Handle for the request.
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the result in "pay" @e mode.
-   */
-  TALER_MERCHANT_PayCallback pay_cb;
-
-  /**
-   * Closure for @a pay_cb.
-   */
-  void *pay_cb_cls;
-
-  /**
-   * Function to call with the result in "abort-refund" @e mode.
-   */
-  TALER_MERCHANT_PayRefundCallback abort_cb;
-
-  /**
-   * Closure for @a abort_cb.
-   */
-  void *abort_cb_cls;
-
-  /**
-   * Operational mode, either "pay" or "abort-refund".
-   */
-  const char *mode;
-
-  /**
-   * Reference to the execution context.
-   */
-  struct GNUNET_CURL_Context *ctx;
-
-  /**
-   * Minor context that holds body and headers.
-   */
-  struct TALER_CURL_PostContext post_ctx;
-
-  /**
-   * The coins we are paying with.
-   */
-  struct TALER_MERCHANT_PaidCoin *coins;
-
-  /**
-   * Number of @e coins we are paying with.
-   */
-  unsigned int num_coins;
-
-  /**
-   * Hash of the contract, only available in "abort-refund" mode.
-   */
-  struct GNUNET_HashCode h_contract_terms;
-
-};
-
-
-/**
- * Check that the response for a /pay refund is well-formed,
- * and call the application callback with the result if it is
- * OK. Otherwise returns #GNUNET_SYSERR.
- *
- * @param ph handle to operation that created the reply
- * @param json the reply to parse
- * @return #GNUNET_OK on success
- */
-static int
-check_abort_refund (struct TALER_MERCHANT_Pay *ph,
-                    const json_t *json)
-{
-  json_t *refunds;
-  unsigned int num_refunds;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_json ("refund_permissions", &refunds),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (json,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  num_refunds = json_array_size (refunds);
-  {
-    struct TALER_MERCHANT_RefundEntry res[GNUNET_NZL (num_refunds)];
-
-    for (unsigned int i = 0; i<num_refunds; i++)
-    {
-      struct TALER_MerchantSignatureP *sig = &res[i].merchant_sig;
-      json_t *refund = json_array_get (refunds, i);
-      struct GNUNET_JSON_Specification spec_detail[] = {
-        GNUNET_JSON_spec_fixed_auto ("merchant_sig",
-                                     sig),
-        GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                     &res[i].coin_pub),
-        GNUNET_JSON_spec_uint64 ("rtransaction_id",
-                                 &res[i].rtransaction_id),
-        GNUNET_JSON_spec_end ()
-      };
-      int found;
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (refund,
-                             spec_detail,
-                             NULL, NULL))
-      {
-        GNUNET_break_op (0);
-        GNUNET_JSON_parse_free (spec);
-        return GNUNET_SYSERR;
-      }
-
-      found = -1;
-      for (unsigned int j = 0; j<ph->num_coins; j++)
-      {
-        if (0 == memcmp (&ph->coins[j].coin_pub,
-                         &res[i].coin_pub,
-                         sizeof
-                         (struct TALER_CoinSpendPublicKeyP)))
-        {
-          found = j;
-          break;
-        }
-      }
-      if (-1 == found)
-      {
-        GNUNET_break_op (0);
-        GNUNET_JSON_parse_free (spec);
-        return GNUNET_SYSERR;
-      }
-
-      {
-        struct TALER_RefundRequestPS rr = {
-          .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
-          .purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)),
-          .h_contract_terms = ph->h_contract_terms,
-          .coin_pub = res[i].coin_pub,
-          .merchant = merchant_pub,
-          .rtransaction_id = GNUNET_htonll (res[i].rtransaction_id)
-        };
-
-        TALER_amount_hton (&rr.refund_amount,
-                           &ph->coins[found].amount_with_fee);
-        TALER_amount_hton (&rr.refund_fee,
-                           &ph->coins[found].refund_fee);
-        if (GNUNET_OK !=
-            GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
-                                        &rr,
-                                        &sig->eddsa_sig,
-                                        &merchant_pub.eddsa_pub))
-        {
-          GNUNET_break_op (0);
-          GNUNET_JSON_parse_free (spec);
-          return GNUNET_SYSERR;
-        }
-      }
-    }
-    {
-      struct TALER_MERCHANT_HttpResponse hr = {
-        .reply = json,
-        .http_status = MHD_HTTP_OK
-      };
-
-      ph->abort_cb (ph->abort_cb_cls,
-                    &hr,
-                    &merchant_pub,
-                    &ph->h_contract_terms,
-                    num_refunds,
-                    res);
-    }
-    ph->abort_cb = NULL;
-  }
-  GNUNET_JSON_parse_free (spec);
-  return GNUNET_OK;
-}
-
-
-/**
- * We got a 403 response back from the exchange (or the merchant).
- * Now we need to check the provided cryptographic proof that the
- * coin was actually already spent!
- *
- * @param pc handle of the original coin we paid with
- * @param json cryptographic proof of coin's transaction
- *        history as was returned by the exchange/merchant
- * @return #GNUNET_OK if proof checks out
- */
-static int
-check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc,
-                    json_t *json)
-{
-  struct TALER_Amount spent;
-  struct TALER_Amount spent_plus_contrib;
-
-  if (GNUNET_OK !=
-      TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */
-                                          pc->amount_with_fee.currency,
-                                          &pc->coin_pub,
-                                          json,
-                                          &spent))
-  {
-    /* Exchange's history fails to verify */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (0 >
-      TALER_amount_add (&spent_plus_contrib,
-                        &spent,
-                        &pc->amount_with_fee))
-  {
-    /* We got an integer overflow? Bad application! */
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  if (-1 != TALER_amount_cmp (&pc->denom_value,
-                              &spent_plus_contrib))
-  {
-    /* according to our calculations, the transaction should
-       have still worked, exchange error! */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Accepting proof of double-spending\n");
-  return GNUNET_OK;
-}
-
-
-/**
- * We got a 409 response back from the exchange (or the merchant).
- * Now we need to check the provided cryptographic proof that the
- * coin was actually already spent!
- *
- * @param ph handle of the original pay operation
- * @param json cryptographic proof returned by the
- *        exchange/merchant
- * @return #GNUNET_OK if proof checks out
- */
-static int
-check_conflict (struct TALER_MERCHANT_Pay *ph,
-                const json_t *json)
-{
-  json_t *history;
-  json_t *ereply;
-  struct TALER_CoinSpendPublicKeyP coin_pub;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_json ("exchange_reply", &ereply),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
-    GNUNET_JSON_spec_end ()
-  };
-  struct GNUNET_JSON_Specification hspec[] = {
-    GNUNET_JSON_spec_json ("history", &history),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (json,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (ereply,
-                         hspec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_JSON_parse_free (spec);
-
-  for (unsigned int i = 0; i<ph->num_coins; i++)
-  {
-    if (0 == memcmp (&ph->coins[i].coin_pub,
-                     &coin_pub,
-                     sizeof (struct TALER_CoinSpendPublicKeyP)))
-    {
-      int ret;
-
-      ret = check_coin_history (&ph->coins[i],
-                                history);
-      GNUNET_JSON_parse_free (hspec);
-      return ret;
-    }
-  }
-  GNUNET_break_op (0); /* complaint is not about any of the coins
-                          that we actually paid with... */
-  GNUNET_JSON_parse_free (hspec);
-  return GNUNET_SYSERR;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /pay request.
- *
- * @param cls the `struct TALER_MERCHANT_Pay`
- * @param response_code HTTP response code, 0 on error
- * @param json response body, NULL if not in JSON
- */
-static void
-handle_pay_finished (void *cls,
-                     long response_code,
-                     const void *response)
-{
-  struct TALER_MERCHANT_Pay *ph = cls;
-  const json_t *json = response;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
-  };
-
-  ph->job = NULL;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "/pay completed with response code %u\n",
-              (unsigned int) response_code);
-  if (0 == strcasecmp (ph->mode,
-                       "pay"))
-  {
-    switch (response_code)
-    {
-    case 0:
-      hr.ec = TALER_EC_INVALID_RESPONSE;
-      break;
-    case MHD_HTTP_OK:
-      break;
-    /* Tolerating Not Acceptable because sometimes
-     * - especially in tests - we might want to POST
-     * coins one at a time.  */
-    case MHD_HTTP_NOT_ACCEPTABLE:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      break;
-    case MHD_HTTP_BAD_REQUEST:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* This should never happen, either us
-       * or the merchant is buggy (or API version conflict);
-       * just pass JSON reply to the application */
-      break;
-    case MHD_HTTP_FORBIDDEN:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* Nothing really to verify, merchant says we tried to abort the payment
-       * after it was successful. We should pass the JSON reply to the
-       * application */
-      break;
-    case MHD_HTTP_NOT_FOUND:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* Nothing really to verify, this should never
-         happen, we should pass the JSON reply to the
-         application */
-      break;
-    case MHD_HTTP_PRECONDITION_FAILED:
-      TALER_MERCHANT_parse_error_details_ (json,
-                                           response_code,
-                                           &hr);
-      /* Nothing really to verify, the merchant is blaming us for failing to
-         satisfy some constraint (likely it does not like our exchange because
-         of some disagreement on the PKI).  We should pass the JSON reply to 
the
-         application */
-      break;
-    case MHD_HTTP_REQUEST_TIMEOUT:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* The merchant couldn't generate a timely response, likely because
-         it itself waited too long on the exchange.
-         Pass on to application. */
-      break;
-    case MHD_HTTP_CONFLICT:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      if (GNUNET_OK != check_conflict (ph,
-                                       json))
-      {
-        GNUNET_break_op (0);
-        response_code = 0;
-      }
-      break;
-    case MHD_HTTP_GONE:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* The merchant says we are too late, the offer has expired or some
-         denomination key of a coin involved has expired.
-         Might be a disagreement in timestamps? Still, pass on to application. 
*/
-      break;
-    case MHD_HTTP_FAILED_DEPENDENCY:
-      TALER_MERCHANT_parse_error_details_ (json,
-                                           response_code,
-                                           &hr);
-      /* Nothing really to verify, the merchant is blaming the exchange.
-         We should pass the JSON reply to the application */
-      break;
-    case MHD_HTTP_INTERNAL_SERVER_ERROR:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* Server had an internal issue; we should retry,
-         but this API leaves this to the application */
-      break;
-    case MHD_HTTP_SERVICE_UNAVAILABLE:
-      TALER_MERCHANT_parse_error_details_ (json,
-                                           response_code,
-                                           &hr);
-      /* Exchange couldn't respond properly; the retry is
-         left to the application */
-      break;
-    default:
-      TALER_MERCHANT_parse_error_details_ (json,
-                                           response_code,
-                                           &hr);
-      /* unexpected response code */
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Unexpected response code %u/%d\n",
-                  (unsigned int) response_code,
-                  (int) hr.ec);
-      GNUNET_break_op (0);
-      break;
-    }
-    ph->pay_cb (ph->pay_cb_cls,
-                &hr);
-  }
-  else
-  {
-    GNUNET_assert (0 == strcasecmp (ph->mode,
-                                    "abort-refund"));
-
-    switch (response_code)
-    {
-    case 0:
-      hr.ec = TALER_EC_INVALID_RESPONSE;
-      break;
-    case MHD_HTTP_OK:
-      if (GNUNET_OK ==
-          check_abort_refund (ph,
-                              json))
-      {
-        TALER_MERCHANT_pay_cancel (ph);
-        return;
-      }
-      hr.http_status = 0;
-      hr.ec = TALER_EC_PAY_MERCHANT_INVALID_RESPONSE;
-      break;
-    case MHD_HTTP_BAD_REQUEST:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* This should never happen, either us or the
-         merchant is buggy (or API version conflict); just
-         pass JSON reply to the application */
-      break;
-    case MHD_HTTP_CONFLICT:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      break;
-    case MHD_HTTP_FORBIDDEN:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* Nothing really to verify, merchant says one of
-         the signatures is invalid; as we checked them,
-         this should never happen, we should pass the JSON
-         reply to the application */
-      break;
-    case MHD_HTTP_NOT_FOUND:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* Nothing really to verify, this should never
-   happen, we should pass the JSON reply to the
-         application */
-      break;
-    case MHD_HTTP_FAILED_DEPENDENCY:
-      TALER_MERCHANT_parse_error_details_ (json,
-                                           response_code,
-                                           &hr);
-      /* Nothing really to verify, the merchant is blaming the exchange.
-         We should pass the JSON reply to the application */
-      break;
-    case MHD_HTTP_INTERNAL_SERVER_ERROR:
-      hr.ec = TALER_JSON_get_error_code (json);
-      hr.hint = TALER_JSON_get_error_hint (json);
-      /* Server had an internal issue; we should retry,
-         but this API leaves this to the application */
-      break;
-    default:
-      /* unexpected response code */
-      TALER_MERCHANT_parse_error_details_ (json,
-                                           response_code,
-                                           &hr);
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Unexpected response code %u/%d\n",
-                  (unsigned int) response_code,
-                  (int) hr.ec);
-      GNUNET_break_op (0);
-      break;
-    }
-    ph->abort_cb (ph->abort_cb_cls,
-                  &hr,
-                  NULL,
-                  NULL,
-                  0,
-                  NULL);
-  }
-  TALER_MERCHANT_pay_cancel (ph);
-}
-
-
-/**
- * Issue /pay request. Generic version for the various
- * variants of the API.
- *
- * @param ctx the execution loop context
- * @param merchant_url base URL of the merchant's backend
- * @param merchant_pub public key of the merchant
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param mode mode string to use ("pay" or "abort-refund").
- * @param pay_cb the callback to call when a reply for this
- *        request is available
- * @param pay_cb_cls closure for @a pay_cb
- * @param abort_cb callback to call for the abort-refund variant
- * @param abort_cb_cls closure for @a abort_cb
- * @return a handle for this request
- */
-static struct TALER_MERCHANT_Pay *
-request_pay_generic (
-  struct GNUNET_CURL_Context *ctx,
-  const char *merchant_url,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const char *order_id,
-  unsigned int num_coins,
-  const struct TALER_MERCHANT_PaidCoin *coins,
-  const char *mode,
-  TALER_MERCHANT_PayCallback pay_cb,
-  void *pay_cb_cls,
-  TALER_MERCHANT_PayRefundCallback abort_cb,
-  void *abort_cb_cls)
-{
-  struct TALER_MERCHANT_Pay *ph;
-  json_t *pay_obj;
-  json_t *j_coins;
-  CURL *eh;
-  struct TALER_Amount total_fee;
-  struct TALER_Amount total_amount;
-
-  if (0 == num_coins)
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  j_coins = json_array ();
-  for (unsigned int i = 0; i<num_coins; i++)
-  {
-    json_t *j_coin;
-    const struct TALER_MERCHANT_PaidCoin *pc = &coins[i];
-    struct TALER_Amount fee;
-
-    if (0 >
-        TALER_amount_subtract (&fee,
-                               &pc->amount_with_fee,
-                               &pc->amount_without_fee))
-    {
-      /* Integer underflow, fee larger than total amount?
-         This should not happen (client violated API!) */
-      GNUNET_break (0);
-      json_decref (j_coins);
-      return NULL;
-    }
-    if (0 == i)
-    {
-      total_fee = fee;
-      total_amount = pc->amount_with_fee;
-    }
-    else
-    {
-      if ( (0 >
-            TALER_amount_add (&total_fee,
-                              &total_fee,
-                              &fee)) ||
-           (0 >
-            TALER_amount_add (&total_amount,
-                              &total_amount,
-                              &pc->amount_with_fee)) )
-      {
-        /* integer overflow */
-        GNUNET_break (0);
-        json_decref (j_coins);
-        return NULL;
-      }
-    }
-
-    /* create JSON for this coin */
-    j_coin = json_pack (
-      "{s:o, s:o,"                   /* contribution/coin_pub */
-      " s:s, s:o,"          /* exchange_url / denom_pub */
-      " s:o, s:o}",          /* ub_sig / coin_sig */
-      "contribution",
-      TALER_JSON_from_amount (&pc->amount_with_fee),
-      "coin_pub",
-      GNUNET_JSON_from_data_auto (&pc->coin_pub),
-      "exchange_url",
-      pc->exchange_url,
-      "denom_pub",
-      GNUNET_JSON_from_rsa_public_key (pc->denom_pub.rsa_public_key),
-      "ub_sig",
-      GNUNET_JSON_from_rsa_signature (pc->denom_sig.rsa_signature),
-      "coin_sig",
-      GNUNET_JSON_from_data_auto (&pc->coin_sig));
-    if (0 !=
-        json_array_append_new (j_coins,
-                               j_coin))
-    {
-      GNUNET_break (0);
-      json_decref (j_coins);
-      return NULL;
-    }
-  }
-
-  pay_obj = json_pack ("{"
-                       " s:s," /* mode */
-                       " s:o," /* coins */
-                       " s:s," /* order_id */
-                       " s:o," /* merchant_pub */
-                       "}",
-                       "mode",
-                       mode,
-                       "coins",
-                       j_coins, /* reference consumed! */
-                       "order_id",
-                       order_id,
-                       "merchant_pub",
-                       GNUNET_JSON_from_data_auto (merchant_pub));
-  if (NULL == pay_obj)
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  ph = GNUNET_new (struct TALER_MERCHANT_Pay);
-  ph->ctx = ctx;
-  ph->mode = mode;
-  ph->abort_cb = abort_cb;
-  ph->abort_cb_cls = abort_cb_cls;
-  ph->pay_cb = pay_cb;
-  ph->pay_cb_cls = pay_cb_cls;
-  ph->url = TALER_url_join (merchant_url, "pay", NULL);
-  if (NULL == ph->url)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not construct request URL.\n");
-    json_decref (pay_obj);
-    GNUNET_free (ph);
-    return NULL;
-  }
-  ph->num_coins = num_coins;
-  ph->coins = GNUNET_new_array (num_coins,
-                                struct TALER_MERCHANT_PaidCoin);
-  memcpy (ph->coins,
-          coins,
-          num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
-
-  eh = curl_easy_init ();
-  if (GNUNET_OK != TALER_curl_easy_post (&ph->post_ctx,
-                                         eh,
-                                         pay_obj))
-  {
-    GNUNET_break (0);
-    json_decref (pay_obj);
-    GNUNET_free (ph);
-    return NULL;
-  }
-
-  json_decref (pay_obj);
-  GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
-                                               CURLOPT_URL,
-                                               ph->url));
-  ph->job = GNUNET_CURL_job_add2 (ctx,
-                                  eh,
-                                  ph->post_ctx.headers,
-                                  &handle_pay_finished,
-                                  ph);
-  return ph;
-}
-
-
-/**
- * Pay a merchant.  API for wallets that have the coin's private
- * keys.
- * _NOTE_: this function does NOT calculate each coin amount in
- * order to match the contract total price.  This calculation is
- * to be made by the logic using this library.
- *
- * @param ctx the execution loop context
- * @param merchant_url base URL of the merchant's backend
- * @param h_contract_terms hashcode of the proposal being paid
- * @param amount total value of the contract to be paid to the
- *        merchant
- * @param max_fee maximum fee covered by the merchant
- *        (according to the contract)
- * @param merchant_pub the public key of the merchant
- *        (used to identify the merchant for refund requests)
- * @param merchant_sig signature from the merchant over the
- *        original contract
- * @param timestamp timestamp when the contract was finalized,
- *        must match approximately the current time of the merchant
- * @param refund_deadline date until which the merchant can issue
- *        a refund to the customer via the merchant (can be zero
- *        if refunds are not allowed)
- * @param pay_deadline maximum time limit to pay for this contract
- * @param h_wire hash of the merchant’s account details
- * @param order_id order id of the proposal being paid
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param pay_cb the callback to call when a reply for this
- *        request is available
- * @param pay_cb_cls closure for @a pay_cb
- * @return a handle for this request
- */
-static struct TALER_MERCHANT_Pay *
-prepare_pay_generic (struct GNUNET_CURL_Context *ctx,
-                     const char *merchant_url,
-                     const struct GNUNET_HashCode *h_contract_terms,
-                     const struct TALER_Amount *amount,
-                     const struct TALER_Amount *max_fee,
-                     const struct TALER_MerchantPublicKeyP *merchant_pub,
-                     const struct TALER_MerchantSignatureP *merchant_sig,
-                     struct GNUNET_TIME_Absolute timestamp,
-                     struct GNUNET_TIME_Absolute refund_deadline,
-                     struct GNUNET_TIME_Absolute pay_deadline,
-                     const struct GNUNET_HashCode *h_wire,
-                     const char *order_id,
-                     unsigned int num_coins,
-                     const struct TALER_MERCHANT_PayCoin *coins,
-                     const char *mode,
-                     TALER_MERCHANT_PayCallback pay_cb,
-                     void *pay_cb_cls,
-                     TALER_MERCHANT_PayRefundCallback abort_cb,
-                     void *abort_cb_cls)
-{
-  struct TALER_DepositRequestPS dr;
-  struct TALER_MERCHANT_PaidCoin pc[num_coins];
-
-  (void) GNUNET_TIME_round_abs (&timestamp);
-  (void) GNUNET_TIME_round_abs (&pay_deadline);
-  (void) GNUNET_TIME_round_abs (&refund_deadline);
-
-  if (GNUNET_YES !=
-      TALER_amount_cmp_currency (amount,
-                                 max_fee))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-
-  dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
-  dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS));
-  dr.h_contract_terms = *h_contract_terms;
-  dr.h_wire = *h_wire;
-  dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
-  dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
-  dr.merchant = *merchant_pub;
-
-  for (unsigned int i = 0; i<num_coins; i++)
-  {
-    const struct TALER_MERCHANT_PayCoin *coin = &coins[i]; // coin priv.
-    struct TALER_MERCHANT_PaidCoin *p = &pc[i]; // coin pub.
-    struct TALER_Amount fee;
-
-    /* prepare 'dr' for this coin to generate coin signature */
-    GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv,
-                                        &dr.coin_pub.eddsa_pub);
-    TALER_amount_hton (&dr.amount_with_fee,
-                       &coin->amount_with_fee);
-    if (0 >
-        TALER_amount_subtract (&fee,
-                               &coin->amount_with_fee,
-                               &coin->amount_without_fee))
-    {
-      /* Integer underflow, fee larger than total amount?
-         This should not happen (client violated API!) */
-      GNUNET_break (0);
-      return NULL;
-    }
-    TALER_amount_hton (&dr.deposit_fee,
-                       &fee);
-    {
-      TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
-                       TALER_amount2s (&coin->amount_with_fee));
-      TALER_LOG_DEBUG ("... fee was %s\n",
-                       TALER_amount2s (&fee));
-    }
-
-    GNUNET_CRYPTO_eddsa_sign (&coin->coin_priv.eddsa_priv,
-                              &dr,
-                              &p->coin_sig.eddsa_signature);
-    p->denom_pub = coin->denom_pub;
-    p->denom_sig = coin->denom_sig;
-    p->denom_value = coin->denom_value;
-    p->coin_pub = dr.coin_pub;
-    p->amount_with_fee = coin->amount_with_fee;
-    p->amount_without_fee = coin->amount_without_fee;
-    p->refund_fee = coin->refund_fee;
-    p->exchange_url = coin->exchange_url;
-  }
-  return request_pay_generic (ctx,
-                              merchant_url,
-                              merchant_pub,
-                              order_id,
-                              num_coins,
-                              pc,
-                              mode,
-                              pay_cb,
-                              pay_cb_cls,
-                              abort_cb,
-                              abort_cb_cls);
-}
-
-
-/**
- * Pay a merchant.  API for wallets that have the coin's private keys.
- * _NOTE_: this function does NOT calculate each coin amount in order
- * to match the contract total price.  This calculation is to be made
- * by the logic using this library.
- *
- * @param ctx the execution loop context
- * @param merchant_url base URL of the merchant's backend
- * @param h_contract_terms hashcode of the proposal being paid
- * @param amount total value of the contract to be paid to the merchant
- * @param max_fee maximum fee covered by the merchant (according to the 
contract)
- * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
- * @param merchant_sig signature from the merchant over the original contract
- * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
- * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
- * @param pay_deadline maximum time limit to pay for this contract
- * @param h_wire hash of the merchant’s account details
- * @param order_id order id of the proposal being paid
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param pay_cb the callback to call when a reply for this request is 
available
- * @param pay_cb_cls closure for @a pay_cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
-                           const char *merchant_url,
-                           const struct GNUNET_HashCode *h_contract_terms,
-                           const struct TALER_Amount *amount,
-                           const struct TALER_Amount *max_fee,
-                           const struct TALER_MerchantPublicKeyP *merchant_pub,
-                           const struct TALER_MerchantSignatureP *merchant_sig,
-                           struct GNUNET_TIME_Absolute timestamp,
-                           struct GNUNET_TIME_Absolute refund_deadline,
-                           struct GNUNET_TIME_Absolute pay_deadline,
-                           const struct GNUNET_HashCode *h_wire,
-                           const char *order_id,
-                           unsigned int num_coins,
-                           const struct TALER_MERCHANT_PayCoin *coins,
-                           TALER_MERCHANT_PayCallback pay_cb,
-                           void *pay_cb_cls)
-{
-  return prepare_pay_generic (ctx,
-                              merchant_url,
-                              h_contract_terms,
-                              amount,
-                              max_fee,
-                              merchant_pub,
-                              merchant_sig,
-                              timestamp,
-                              refund_deadline,
-                              pay_deadline,
-                              h_wire,
-                              order_id,
-                              num_coins,
-                              coins,
-                              "pay",
-                              pay_cb,
-                              pay_cb_cls,
-                              NULL,
-                              NULL);
-}
-
-
-/**
- * Run a payment abort operation, asking for refunds for coins
- * that were previously spend on a /pay that failed to go through.
- *
- * @param ctx execution context
- * @param merchant_url base URL of the merchant
- * @param h_wire hash of the merchant’s account details
- * @param h_contract hash of the contact of the merchant with the customer
- * @param transaction_id transaction id for the transaction between merchant 
and customer
- * @param amount total value of the contract to be paid to the merchant
- * @param max_fee maximum fee covered by the merchant (according to the 
contract)
- * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
- * @param merchant_sig signature from the merchant over the original contract
- * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
- * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
- * @param pay_deadline maximum time limit to pay for this contract
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s 
private key.
- * @param payref_cb the callback to call when a reply for this request is 
available
- * @param payref_cb_cls closure for @a pay_cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_abort (struct GNUNET_CURL_Context *ctx,
-                          const char *merchant_url,
-                          const struct GNUNET_HashCode *h_contract,
-                          const struct TALER_Amount *amount,
-                          const struct TALER_Amount *max_fee,
-                          const struct TALER_MerchantPublicKeyP *merchant_pub,
-                          const struct TALER_MerchantSignatureP *merchant_sig,
-                          struct GNUNET_TIME_Absolute timestamp,
-                          struct GNUNET_TIME_Absolute refund_deadline,
-                          struct GNUNET_TIME_Absolute pay_deadline,
-                          const struct GNUNET_HashCode *h_wire,
-                          const char *order_id,
-                          unsigned int num_coins,
-                          const struct TALER_MERCHANT_PayCoin *coins,
-                          TALER_MERCHANT_PayRefundCallback payref_cb,
-                          void *payref_cb_cls)
-{
-  struct TALER_MERCHANT_Pay *ph;
-
-  ph = prepare_pay_generic (ctx,
-                            merchant_url,
-                            h_contract,
-                            amount,
-                            max_fee,
-                            merchant_pub,
-                            merchant_sig,
-                            timestamp,
-                            refund_deadline,
-                            pay_deadline,
-                            h_wire,
-                            order_id,
-                            num_coins,
-                            coins,
-                            "abort-refund",
-                            NULL,
-                            NULL,
-                            payref_cb,
-                            payref_cb_cls);
-  if (NULL == ph)
-    return NULL;
-  ph->h_contract_terms = *h_contract;
-  return ph;
-}
-
-
-/**
- * PAY a merchant.  API for frontends talking to backends. Here,
- * the frontend does not have the coin's private keys, but just
- * the public keys and signatures.  Note the subtle difference
- * in the type of @a coins compared to #TALER_MERCHANT_pay().
- *
- * @param ctx the execution loop context
- * @param merchant_url base URL of the merchant's backend
- * @param merchant_pub public key of the merchant
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param pay_cb the callback to call when a reply for this request is 
available
- * @param pay_cb_cls closure for @a pay_cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_frontend (
-  struct GNUNET_CURL_Context *ctx,
-  const char *merchant_url,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const char *order_id,
-  unsigned int num_coins,
-  const struct TALER_MERCHANT_PaidCoin *coins,
-  TALER_MERCHANT_PayCallback pay_cb,
-  void *pay_cb_cls)
-{
-  return request_pay_generic (ctx,
-                              merchant_url,
-                              merchant_pub,
-                              order_id,
-                              num_coins,
-                              coins,
-                              "pay",
-                              pay_cb,
-                              pay_cb_cls,
-                              NULL,
-                              NULL);
-}
-
-
-/**
- * Cancel a pay permission request.  This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param pay the pay permission request handle
- */
-void
-TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *pay)
-{
-  if (NULL != pay->job)
-  {
-    GNUNET_CURL_job_cancel (pay->job);
-    pay->job = NULL;
-  }
-  TALER_curl_easy_post_finished (&pay->post_ctx);
-  GNUNET_free (pay->coins);
-  GNUNET_free (pay->url);
-  GNUNET_free (pay);
-}
-
-
-/* end of merchant_api_pay.c */
diff --git a/src/lib/merchant_api_poll_payment.c 
b/src/lib/merchant_api_poll_payment.c
deleted file mode 100644
index 6b0190a..0000000
--- a/src/lib/merchant_api_poll_payment.c
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2018, 2019, 2020 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_poll_payment.c
- * @brief Implementation of the /poll-payment GET request
- * @author Christian Grothoff
- * @author Marcello Stanisci
- * @author Florian Dold
- */
-#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>
-
-
-/**
- * @brief A poll payment operation handle
- */
-struct TALER_MERCHANT_PollPaymentOperation
-{
-
-  /**
-   * The url for this request.
-   */
-  char *url;
-
-  /**
-   * Handle for the request.
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the result.
-   */
-  TALER_MERCHANT_PollPaymentCallback 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 GET /poll-payment request.
- *
- * @param cls the `struct TALER_MERCHANT_PollPaymentOperation`
- * @param response_code HTTP response code, 0 on error
- * @param json response body, should be NULL
- */
-static void
-handle_poll_payment_finished (void *cls,
-                              long response_code,
-                              const void *response)
-{
-  struct TALER_MERCHANT_PollPaymentOperation *cpo = cls;
-  struct TALER_Amount refund_amount = { 0 };
-  const json_t *json = response;
-  const json_t *refunded;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
-  };
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount ("refund_amount",
-                            &refund_amount),
-    GNUNET_JSON_spec_end ()
-  };
-
-  cpo->job = NULL;
-
-  switch (response_code)
-  {
-  case MHD_HTTP_NOT_FOUND:
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    cpo->cb (cpo->cb_cls,
-             &hr,
-             GNUNET_NO,
-             GNUNET_NO,
-             NULL,
-             NULL);
-    TALER_MERCHANT_poll_payment_cancel (cpo);
-    return;
-  case MHD_HTTP_OK:
-    /* see below */
-    break;
-  default:
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Polling payment failed with HTTP status code %u/%d\n",
-                (unsigned int) response_code,
-                (int) hr.ec);
-    GNUNET_break_op (0);
-    cpo->cb (cpo->cb_cls,
-             &hr,
-             GNUNET_SYSERR,
-             GNUNET_SYSERR,
-             NULL,
-             NULL);
-    TALER_MERCHANT_poll_payment_cancel (cpo);
-    return;
-  }
-
-  /* HTTP OK */
-  if (! json_boolean_value (json_object_get (json, "paid")))
-  {
-    const char *taler_pay_uri = json_string_value (json_object_get (json,
-                                                                    
"taler_pay_uri"));
-    if (NULL == taler_pay_uri)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "no taler_pay_uri in unpaid poll-payment response\n");
-      GNUNET_break_op (0);
-      hr.http_status = 0;
-      hr.ec = TALER_EC_POLL_PAYMENT_REPLY_MALFORMED;
-      cpo->cb (cpo->cb_cls,
-               &hr,
-               GNUNET_SYSERR,
-               GNUNET_SYSERR,
-               NULL,
-               NULL);
-    }
-    else
-    {
-      cpo->cb (cpo->cb_cls,
-               &hr,
-               GNUNET_NO,
-               GNUNET_NO,
-               NULL,
-               taler_pay_uri);
-    }
-    TALER_MERCHANT_poll_payment_cancel (cpo);
-    return;
-  }
-
-  if ( (NULL == (refunded = json_object_get (json,
-                                             "refunded"))) ||
-       ( (json_true () == refunded) &&
-         (GNUNET_OK !=
-          GNUNET_JSON_parse (json,
-                             spec,
-                             NULL, NULL)) ) )
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "poll payment failed to parse JSON\n");
-    GNUNET_break_op (0);
-    hr.http_status = 0;
-    hr.ec = TALER_EC_POLL_PAYMENT_REPLY_MALFORMED;
-    cpo->cb (cpo->cb_cls,
-             &hr,
-             GNUNET_SYSERR,
-             GNUNET_SYSERR,
-             NULL,
-             NULL);
-    TALER_MERCHANT_poll_payment_cancel (cpo);
-    return;
-  }
-
-  cpo->cb (cpo->cb_cls,
-           &hr,
-           GNUNET_YES,
-           (json_true () == refunded),
-           (json_true () == refunded) ? &refund_amount : NULL,
-           NULL);
-  TALER_MERCHANT_poll_payment_cancel (cpo);
-}
-
-
-/**
- * Issue a /poll-payment request to the backend.  Polls the status
- * of a payment.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param order_id order id to identify the payment
- * @param h_contract hash of the contract for @a order_id
- * @param session_id sesion id for the payment (or NULL if the payment is not 
bound to a session)
- * @param timeout timeout to use in long polling (how long may the server wait 
to reply
- *        before generating an unpaid response). Note that this is just 
provided to
- *        the server, we as client will block until the response comes back or 
until
- *        #TALER_MERCHANT_poll_payment_cancel() is called.
- * @param min_refund long poll for the service to approve a refund exceeding 
this value;
- *        use NULL to not wait for any refund (only for payment). Only makes 
sense
- *        with a non-zero @a timeout.
- * @param poll_payment_cb callback which will work the response gotten from 
the backend
- * @param poll_payment_cb_cls closure to pass to @a poll_payment_cb
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_PollPaymentOperation *
-TALER_MERCHANT_poll_payment (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  const char *order_id,
-  const struct GNUNET_HashCode *h_contract,
-  const char *session_id,
-  struct GNUNET_TIME_Relative timeout,
-  const struct TALER_Amount *min_refund,
-  TALER_MERCHANT_PollPaymentCallback poll_payment_cb,
-  void *poll_payment_cb_cls)
-{
-  struct TALER_MERCHANT_PollPaymentOperation *cpo;
-  CURL *eh;
-  char *h_contract_s;
-  char *timeout_s;
-  unsigned int ts;
-  long tlong;
-
-  GNUNET_assert (NULL != backend_url);
-  GNUNET_assert (NULL != order_id);
-  h_contract_s = GNUNET_STRINGS_data_to_string_alloc (h_contract,
-                                                      sizeof (*h_contract));
-  ts = (unsigned int) (timeout.rel_value_us
-                       / GNUNET_TIME_UNIT_SECONDS.rel_value_us);
-  /* set curl timeout to *our* long poll timeout plus one minute
-     (for network latency and processing delays) */
-  tlong = (long) (GNUNET_TIME_relative_add (timeout,
-                                            GNUNET_TIME_UNIT_MINUTES).
-                  rel_value_us
-                  / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
-  GNUNET_asprintf (&timeout_s,
-                   "%u",
-                   ts);
-  cpo = GNUNET_new (struct TALER_MERCHANT_PollPaymentOperation);
-  cpo->ctx = ctx;
-  cpo->cb = poll_payment_cb;
-  cpo->cb_cls = poll_payment_cb_cls;
-  cpo->url = TALER_url_join (backend_url,
-                             "public/poll-payment",
-                             "order_id", order_id,
-                             "session_id", session_id,
-                             "h_contract", h_contract_s,
-                             (0 != ts) ? "timeout" : NULL,
-                             timeout_s,
-                             (NULL != min_refund) ? "refund" : NULL,
-                             (NULL != min_refund) ? TALER_amount2s (
-                               min_refund) : NULL,
-                             NULL);
-  GNUNET_free (h_contract_s);
-  GNUNET_free (timeout_s);
-  if (NULL == cpo->url)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not construct request URL.\n");
-    GNUNET_free (cpo);
-    return NULL;
-  }
-  eh = curl_easy_init ();
-  if (CURLE_OK != curl_easy_setopt (eh,
-                                    CURLOPT_URL,
-                                    cpo->url))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  if (CURLE_OK != curl_easy_setopt (eh,
-                                    CURLOPT_TIMEOUT_MS,
-                                    tlong))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "polling payment from %s\n",
-              cpo->url);
-
-  if (NULL == (cpo->job = GNUNET_CURL_job_add (ctx,
-                                               eh,
-                                               GNUNET_YES,
-                                               &handle_poll_payment_finished,
-                                               cpo)))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  return cpo;
-}
-
-
-/**
- * Cancel a GET /poll-payment request.
- *
- * @param cph handle to the request to be canceled
- */
-void
-TALER_MERCHANT_poll_payment_cancel (
-  struct TALER_MERCHANT_PollPaymentOperation *cph)
-{
-  if (NULL != cph->job)
-  {
-    GNUNET_CURL_job_cancel (cph->job);
-    cph->job = NULL;
-  }
-  GNUNET_free (cph->url);
-  GNUNET_free (cph);
-}
-
-
-/* end of merchant_api_poll_payment.c */
diff --git a/src/lib/merchant_api_post_instances.c 
b/src/lib/merchant_api_post_instances.c
new file mode 100644
index 0000000..55a45e5
--- /dev/null
+++ b/src/lib/merchant_api_post_instances.c
@@ -0,0 +1,307 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_post_instances.c
+ * @brief Implementation of the POST /instances request
+ *        of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /instances/$ID operation.
+ */
+struct TALER_MERCHANT_InstancesPostHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_InstancesPostCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /instances request.
+ *
+ * @param cls the `struct TALER_MERCHANT_InstancesPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_post_instances_finished (void *cls,
+                                long response_code,
+                                const void *response)
+{
+  struct TALER_MERCHANT_InstancesPostHandle *iph = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  iph->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "POST /instances completed with response code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* This should never happen, either us
+     * or the merchant is buggy (or API version conflict);
+     * just pass JSON reply to the application */
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, merchant says we tried to abort the payment
+     * after it was successful. We should pass the JSON reply to the
+     * application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the
+       application */
+    break;
+  case MHD_HTTP_CONFLICT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Server had an internal issue; we should retry,
+       but this API leaves this to the application */
+    break;
+  default:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  iph->cb (iph->cb_cls,
+           &hr);
+  TALER_MERCHANT_instances_post_cancel (iph);
+}
+
+
+/**
+ * Setup an new instance in the backend.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param instance_id identity of the instance to get information about
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param cb function to call with the
+ *        backend's instances information
+ * @param cb_cls closure for @a config_cb
+ * @return the instances handle; NULL upon error
+ */
+struct TALER_MERCHANT_InstancesPostHandle *
+TALER_MERCHANT_instances_post (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *instance_id,
+  unsigned int accounts_length,
+  const char *payto_uris[],
+  const char *name,
+  const json_t *address,
+  const json_t *jurisdiction,
+  const struct TALER_Amount *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const struct TALER_Amount *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  TALER_MERCHANT_InstancesPostCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_InstancesPostHandle *iph;
+  json_t *jpayto_uris;
+  json_t *req_obj;
+
+  jpayto_uris = json_array ();
+  if (NULL == jpayto_uris)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  for (unsigned int i = 0; i<accounts_length; i++)
+  {
+    if (0 !=
+        json_array_append_new (jpayto_uris,
+                               json_string (payto_uris[i])))
+    {
+      GNUNET_break (0);
+      json_decref (jpayto_uris);
+      return NULL;
+    }
+  }
+  req_obj = json_pack ("{s:o, s:s, s:s, s:O, s:O"
+                       " s:o, s:I: s:o, s:o, s:o}",
+                       "payto_uris",
+                       jpayto_uris,
+                       "id",
+                       instance_id,
+                       "name",
+                       name,
+                       "address",
+                       address,
+                       "jurisdiction",
+                       jurisdiction,
+                       /* end of group of 5 */
+                       "default_max_wire_fee",
+                       TALER_JSON_from_amount (default_max_wire_fee),
+                       "default_wire_fee_amortization",
+                       (json_int_t) default_wire_fee_amortization,
+                       "default_max_deposit_fee",
+                       TALER_JSON_from_amount (default_max_deposit_fee),
+                       "default_wire_transfer_delay",
+                       GNUNET_JSON_from_time_rel (default_wire_transfer_delay),
+                       "default_pay_delay",
+                       GNUNET_JSON_from_time_rel (default_pay_delay));
+  if (NULL == req_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  iph = GNUNET_new (struct TALER_MERCHANT_InstancesPostHandle);
+  iph->ctx = ctx;
+  iph->cb = cb;
+  iph->cb_cls = cb_cls;
+  iph->url = TALER_url_join (backend_url,
+                             "private/instances",
+                             NULL);
+  if (NULL == iph->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    json_decref (req_obj);
+    GNUNET_free (iph);
+    return NULL;
+  }
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&iph->post_ctx,
+                              eh,
+                              req_obj))
+    {
+      GNUNET_break (0);
+      json_decref (req_obj);
+      GNUNET_free (iph);
+      return NULL;
+    }
+
+    json_decref (req_obj);
+    GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
+                                                 CURLOPT_URL,
+                                                 iph->url));
+    iph->job = GNUNET_CURL_job_add2 (ctx,
+                                     eh,
+                                     iph->post_ctx.headers,
+                                     &handle_post_instances_finished,
+                                     iph);
+  }
+  return iph;
+}
+
+
+/**
+ * Cancel /instances request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param igh request to cancel.
+ */
+void
+TALER_MERCHANT_instances_post_cancel (
+  struct TALER_MERCHANT_InstancesPostHandle *iph)
+{
+  if (NULL != iph->job)
+  {
+    GNUNET_CURL_job_cancel (iph->job);
+    iph->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&iph->post_ctx);
+  GNUNET_free (iph->url);
+  GNUNET_free (iph);
+}
+
+
+/* end of merchant_api_post_instances.c */
diff --git a/src/lib/merchant_api_post_order_abort.c 
b/src/lib/merchant_api_post_order_abort.c
new file mode 100644
index 0000000..f3c8224
--- /dev/null
+++ b/src/lib/merchant_api_post_order_abort.c
@@ -0,0 +1,482 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014, 2015, 2016, 2017, 2020 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_post_order_abort.c
+ * @brief Implementation of the POST /orders/$ID/abort request
+ *        of the merchant's HTTP API
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#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 <taler/taler_exchange_service.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * @brief An abort Handle
+ */
+struct TALER_MERCHANT_OrderAbortHandle
+{
+  /**
+   * Hash of the contract.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * Public key of the merchant.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_AbortCallback abort_cb;
+
+  /**
+   * Closure for @a abort_cb.
+   */
+  void *abort_cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * The coins we are aborting on.
+   */
+  struct TALER_MERCHANT_AbortCoin *coins;
+
+  /**
+   * Number of @e coins we are paying with.
+   */
+  unsigned int num_coins;
+
+};
+
+
+/**
+ * Check that the response for an abort is well-formed,
+ * and call the application callback with the result if it is
+ * OK. Otherwise returns #GNUNET_SYSERR.
+ *
+ * @param oah handle to operation that created the reply
+ * @param json the reply to parse
+ * @return #GNUNET_OK on success
+ */
+static int
+check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
+                    const json_t *json)
+{
+  json_t *refunds;
+  unsigned int num_refunds;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("refunds", &refunds),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (! json_is_array (refunds))
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (spec);
+    return GNUNET_SYSERR;
+  }
+  num_refunds = json_array_size (refunds);
+  {
+    struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)];
+
+    for (unsigned int i = 0; i<num_refunds; i++)
+    {
+      json_t *refund = json_array_get (refunds, i);
+      uint32_t exchange_status;
+      json_t *exchange_reply;
+      struct GNUNET_JSON_Specification spec_es[] = {
+        GNUNET_JSON_spec_uint32 ("exchange_http_status",
+                                 &exchange_status),
+        GNUNET_JSON_spec_json ("exchange_reply",
+                               &exchange_reply),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (refund,
+                             spec_es,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        GNUNET_JSON_parse_free (spec);
+        return GNUNET_SYSERR;
+      }
+      if (MHD_HTTP_OK == exchange_status)
+      {
+        struct GNUNET_JSON_Specification spec_detail[] = {
+          GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                       &res[i].exchange_sig),
+          GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                       &res[i].exchange_pub),
+          TALER_JSON_spec_amount ("refund_fee",
+                                  &res[i].refund_fee),
+          GNUNET_JSON_spec_end ()
+        };
+
+        if (GNUNET_OK !=
+            GNUNET_JSON_parse (exchange_reply,
+                               spec_detail,
+                               NULL, NULL))
+        {
+          GNUNET_break_op (0);
+          GNUNET_JSON_parse_free (spec);
+          return GNUNET_SYSERR;
+        }
+      }
+
+      {
+        struct TALER_RefundConfirmationPS rr = {
+          .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
+          .purpose.size = htonl (sizeof (rr)),
+          .h_contract_terms = oah->h_contract_terms,
+          .coin_pub = oah->coins[i].coin_pub,
+          .merchant = oah->merchant_pub,
+          .rtransaction_id = GNUNET_htonll (0)
+        };
+
+        TALER_amount_hton (&rr.refund_amount,
+                           &oah->coins[i].amount_with_fee);
+        TALER_amount_hton (&rr.refund_fee,
+                           &res[i].refund_fee);
+        if (GNUNET_OK !=
+            GNUNET_CRYPTO_eddsa_verify 
(TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
+                                        &rr,
+                                        &res[i].exchange_sig.eddsa_signature,
+                                        &res[i].exchange_pub.eddsa_pub))
+        {
+          GNUNET_break_op (0);
+          GNUNET_JSON_parse_free (spec);
+          return GNUNET_SYSERR;
+        }
+      }
+    }
+    {
+      struct TALER_MERCHANT_HttpResponse hr = {
+        .reply = json,
+        .http_status = MHD_HTTP_OK
+      };
+
+      oah->abort_cb (oah->abort_cb_cls,
+                     &hr,
+                     &oah->merchant_pub,
+                     num_refunds,
+                     res);
+    }
+    oah->abort_cb = NULL;
+  }
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * abort request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OrderAbortHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_abort_finished (void *cls,
+                       long response_code,
+                       const void *response)
+{
+  struct TALER_MERCHANT_OrderAbortHandle *oah = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  oah->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "/pay completed with response code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK ==
+        check_abort_refund (oah,
+                            json))
+    {
+      TALER_MERCHANT_order_abort_cancel (oah);
+      return;
+    }
+    hr.http_status = 0;
+    hr.ec = TALER_EC_PAY_MERCHANT_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* This should never happen, either us or the
+       merchant is buggy (or API version conflict); just
+       pass JSON reply to the application */
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the
+       application */
+    break;
+  case MHD_HTTP_REQUEST_TIMEOUT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, merchant says one of
+       the signatures is invalid; as we checked them,
+       this should never happen, we should pass the JSON
+       reply to the application */
+    break;
+  case MHD_HTTP_PRECONDITION_FAILED:
+    /* Our *payment* already succeeded fully. */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_FAILED_DEPENDENCY:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* Nothing really to verify, the merchant is blaming the exchange.
+       We should pass the JSON reply to the application */
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Server had an internal issue; we should retry,
+       but this API leaves this to the application */
+    break;
+  default:
+    /* unexpected response code */
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  oah->abort_cb (oah->abort_cb_cls,
+                 &hr,
+                 NULL,
+                 0,
+                 NULL);
+  TALER_MERCHANT_order_abort_cancel (oah);
+}
+
+
+/**
+ * Run an abort operation, asking for refunds for coins
+ * that were previously spend on a/pay that failed to go through.
+ *
+ * @param ctx execution context
+ * @param merchant_url base URL of the merchant
+ * @param order_id order to abort
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_OrderAbortHandle *
+TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
+                            const char *merchant_url,
+                            const char *order_id,
+                            const struct TALER_MerchantPublicKeyP 
*merchant_pub,
+                            const struct GNUNET_HashCode *h_contract,
+                            unsigned int num_coins,
+                            const struct TALER_MERCHANT_AbortCoin coins[],
+                            TALER_MERCHANT_AbortCallback cb,
+                            void *cb_cls)
+{
+  struct TALER_MERCHANT_OrderAbortHandle *oah;
+  json_t *abort_obj;
+  json_t *j_coins;
+
+  j_coins = json_array ();
+  if (NULL == j_coins)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  for (unsigned int i = 0; i<num_coins; i++)
+  {
+    const struct TALER_MERCHANT_AbortCoin *ac = &coins[i];
+    json_t *j_coin;
+
+    /* create JSON for this coin */
+    j_coin = json_pack (
+      "{s:o, s:o,s:s}",
+      "coin_pub",
+      GNUNET_JSON_from_data_auto (&ac->coin_pub),
+      "contribution",
+      TALER_JSON_from_amount (&ac->amount_with_fee),
+      "exchange_url",
+      ac->exchange_url);
+    if ( (NULL == j_coin) ||
+         (0 !=
+          json_array_append_new (j_coins,
+                                 j_coin)) )
+    {
+      GNUNET_break (0);
+      json_decref (j_coins);
+      return NULL;
+    }
+  }
+  abort_obj = json_pack ("{s:o,s:o}",
+                         "coins",
+                         j_coins, /* reference consumed! */
+                         "h_contract",
+                         GNUNET_JSON_from_data_auto (h_contract));
+  if (NULL == abort_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  oah = GNUNET_new (struct TALER_MERCHANT_OrderAbortHandle);
+  oah->h_contract_terms = *h_contract;
+  oah->merchant_pub = *merchant_pub;
+  oah->ctx = ctx;
+  oah->abort_cb = cb;
+  oah->abort_cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "orders/%s/abort",
+                     order_id);
+    oah->url = TALER_url_join (merchant_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == oah->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    json_decref (abort_obj);
+    GNUNET_free (oah);
+    return NULL;
+  }
+  oah->num_coins = num_coins;
+  oah->coins = GNUNET_new_array (num_coins,
+                                 struct TALER_MERCHANT_AbortCoin);
+  memcpy (oah->coins,
+          coins,
+          num_coins * sizeof (struct TALER_MERCHANT_AbortCoin));
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    GNUNET_assert (NULL != eh);
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&oah->post_ctx,
+                              eh,
+                              abort_obj))
+    {
+      GNUNET_break (0);
+      json_decref (abort_obj);
+      GNUNET_free (oah);
+      return NULL;
+    }
+    json_decref (abort_obj);
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_URL,
+                                     oah->url));
+    oah->job = GNUNET_CURL_job_add2 (ctx,
+                                     eh,
+                                     oah->post_ctx.headers,
+                                     &handle_abort_finished,
+                                     oah);
+  }
+  return oah;
+}
+
+
+/**
+ * Cancel an abort request.  This function cannot be used on a request handle
+ * if a response is already served for it.
+ *
+ * @param oah the pay permission request handle
+ */
+void
+TALER_MERCHANT_order_abort_cancel (struct TALER_MERCHANT_OrderAbortHandle *oah)
+{
+  if (NULL != oah->job)
+  {
+    GNUNET_CURL_job_cancel (oah->job);
+    oah->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&oah->post_ctx);
+  GNUNET_free (oah->coins);
+  GNUNET_free (oah->url);
+  GNUNET_free (oah);
+}
+
+
+/* end of merchant_api_post_order_abort.c */
diff --git a/src/lib/merchant_api_proposal_lookup.c 
b/src/lib/merchant_api_post_order_claim.c
similarity index 51%
rename from src/lib/merchant_api_proposal_lookup.c
rename to src/lib/merchant_api_post_order_claim.c
index 0f2b0f3..31be123 100644
--- a/src/lib/merchant_api_proposal_lookup.c
+++ b/src/lib/merchant_api_post_order_claim.c
@@ -17,8 +17,8 @@
   see <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/merchant_api_proposal_lookup.c
- * @brief Implementation of the /proposal GET
+ * @file lib/merchant_api_post_order_claim.c
+ * @brief Implementation of POST /orders/$ID/claim
  * @author Christian Grothoff
  * @author Marcello Stanisci
  */
@@ -35,12 +35,12 @@
 
 
 /**
- * Structure representing a GET /proposal operation.
+ * Structure representing a POST /orders/$ID/claim operation.
  */
-struct TALER_MERCHANT_ProposalLookupOperation
+struct TALER_MERCHANT_OrderClaimHandle
 {
   /**
-   * Full URL, includes "/proposal".
+   * Full URL, includes "/orders/$ID/claim".
    */
   char *url;
 
@@ -52,7 +52,7 @@ struct TALER_MERCHANT_ProposalLookupOperation
   /**
    * Function to call with the result.
    */
-  TALER_MERCHANT_ProposalLookupOperationCallback cb;
+  TALER_MERCHANT_OrderClaimCallback cb;
 
   /**
    * Closure for @a cb.
@@ -65,31 +65,26 @@ struct TALER_MERCHANT_ProposalLookupOperation
   struct GNUNET_CURL_Context *ctx;
 
   /**
-   * Should we send the lookup operation with a nonce?
+   * Minor context that holds body and headers.
    */
-  int has_nonce;
-
-  /**
-   * Nonce, only initialized if has_nonce is GNUNET_YES.
-   */
-  struct GNUNET_CRYPTO_EddsaPublicKey nonce;
-
+  struct TALER_CURL_PostContext post_ctx;
 };
 
 
 /**
- * Function called when we're done processing the GET /proposal request.
+ * Function called when we're done processing the
+ * POST /orders/$ID/claim request.
  *
- * @param cls the `struct TALER_MERCHANT_ProposalLookupOperation`
+ * @param cls the `struct TALER_MERCHANT_OrderClaimHandle`
  * @param response_code HTTP response code, 0 on error
  * @param json response body, should be NULL
  */
 static void
-handle_proposal_lookup_finished (void *cls,
-                                 long response_code,
-                                 const void *response)
+handle_post_order_claim_finished (void *cls,
+                                  long response_code,
+                                  const void *response)
 {
-  struct TALER_MERCHANT_ProposalLookupOperation *plo = cls;
+  struct TALER_MERCHANT_OrderClaimHandle *och = cls;
   json_t *contract_terms;
   struct TALER_MerchantSignatureP sig;
   struct GNUNET_HashCode hash;
@@ -106,7 +101,7 @@ handle_proposal_lookup_finished (void *cls,
     .reply = json
   };
 
-  plo->job = NULL;
+  och->job = NULL;
   if (MHD_HTTP_OK != response_code)
   {
     hr.ec = TALER_JSON_get_error_code (json);
@@ -115,12 +110,12 @@ handle_proposal_lookup_finished (void *cls,
                 "Proposal lookup failed with HTTP status code %u/%d\n",
                 (unsigned int) response_code,
                 (int) hr.ec);
-    plo->cb (plo->cb_cls,
+    och->cb (och->cb_cls,
              &hr,
              NULL,
              NULL,
              NULL);
-    TALER_MERCHANT_proposal_lookup_cancel (plo);
+    TALER_MERCHANT_order_claim_cancel (och);
     return;
   }
 
@@ -130,16 +125,16 @@ handle_proposal_lookup_finished (void *cls,
                          NULL, NULL))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "proposal lookup failed to parse JSON\n");
+                "Claiming order failed: could not parse JSON response\n");
     GNUNET_break_op (0);
     hr.ec = TALER_EC_INVALID_RESPONSE;
     hr.http_status = 0;
-    plo->cb (plo->cb_cls,
+    och->cb (och->cb_cls,
              &hr,
              NULL,
              NULL,
              NULL);
-    TALER_MERCHANT_proposal_lookup_cancel (plo);
+    TALER_MERCHANT_order_claim_cancel (och);
     return;
   }
 
@@ -151,27 +146,22 @@ handle_proposal_lookup_finished (void *cls,
     hr.ec = TALER_EC_CLIENT_INTERNAL_FAILURE;
     hr.http_status = 0;
     GNUNET_JSON_parse_free (spec);
-    plo->cb (plo->cb_cls,
+    och->cb (och->cb_cls,
              &hr,
              NULL,
              NULL,
              NULL);
-    TALER_MERCHANT_proposal_lookup_cancel (plo);
+    TALER_MERCHANT_order_claim_cancel (och);
     return;
   }
 
-  plo->job = NULL;
-  /**
-   * As no data is supposed to be extracted from this
-   * call, we just invoke the provided callback.
-   */
-  plo->cb (plo->cb_cls,
+  och->cb (och->cb_cls,
            &hr,
            contract_terms,
            &sig,
            &hash);
   GNUNET_JSON_parse_free (spec);
-  TALER_MERCHANT_proposal_lookup_cancel (plo);
+  TALER_MERCHANT_order_claim_cancel (och);
 }
 
 
@@ -183,97 +173,97 @@ handle_proposal_lookup_finished (void *cls,
  * @param backend_url base URL of the merchant backend
  * @param order_id order id used to perform the lookup
  * @param nonce nonce used to perform the lookup
- * @param plo_cb callback which will work the response gotten from the backend
- * @param plo_cb_cls closure to pass to @a history_cb
- * @return handle for this operation, NULL upon errors
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to @a cb
+ * @return handle for this handle, NULL upon errors
  */
-struct TALER_MERCHANT_ProposalLookupOperation *
-TALER_MERCHANT_proposal_lookup (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  const char *order_id,
-  const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
-  TALER_MERCHANT_ProposalLookupOperationCallback plo_cb,
-  void *plo_cb_cls)
+struct TALER_MERCHANT_OrderClaimHandle *
+TALER_MERCHANT_order_claim (struct GNUNET_CURL_Context *ctx,
+                            const char *backend_url,
+                            const char *order_id,
+                            const struct GNUNET_CRYPTO_EddsaPublicKey *nonce,
+                            TALER_MERCHANT_OrderClaimCallback cb,
+                            void *cb_cls)
 {
-  struct TALER_MERCHANT_ProposalLookupOperation *plo;
-  CURL *eh;
-  char *nonce_str = NULL;
+  struct TALER_MERCHANT_OrderClaimHandle *och;
+  json_t *req_obj;
 
-  plo = GNUNET_new (struct TALER_MERCHANT_ProposalLookupOperation);
-  plo->ctx = ctx;
-  plo->cb = plo_cb;
-  plo->cb_cls = plo_cb_cls;
-  if (NULL != nonce)
+  req_obj = json_pack ("{s:o}",
+                       "nonce",
+                       GNUNET_JSON_from_data_auto (nonce));
+  if (NULL == req_obj)
   {
-    plo->has_nonce = GNUNET_YES;
-    plo->nonce = *nonce;
-    nonce_str = GNUNET_STRINGS_data_to_string_alloc (
-      nonce,
-      sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+    GNUNET_break (0);
+    return NULL;
   }
-  plo->url = TALER_url_join (backend_url,
-                             "proposal",
-                             "order_id",
-                             order_id,
-                             "nonce",
-                             nonce_str,
-                             NULL);
-  GNUNET_free_non_null (nonce_str);
-  if (NULL == plo->url)
+  och = GNUNET_new (struct TALER_MERCHANT_OrderClaimHandle);
+  och->ctx = ctx;
+  och->cb = cb;
+  och->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "orders/%s/claim",
+                     order_id);
+    och->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == och->url)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Could not construct request URL.\n");
-    GNUNET_free (plo);
+    json_decref (req_obj);
+    GNUNET_free (och);
     return NULL;
   }
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "looking up proposal from %s\n",
-              plo->url);
-  eh = curl_easy_init ();
-  if (CURLE_OK != curl_easy_setopt (eh,
-                                    CURLOPT_URL,
-                                    plo->url))
+              "Claiming order at %s\n",
+              och->url);
   {
-    GNUNET_break (0);
-    curl_easy_cleanup (eh);
-    GNUNET_free (plo->url);
-    GNUNET_free (plo);
-    return NULL;
-  }
+    CURL *eh;
 
-  if (NULL == (plo->job = GNUNET_CURL_job_add (ctx,
-                                               eh,
-                                               GNUNET_YES,
-                                               
&handle_proposal_lookup_finished,
-                                               plo)))
-  {
-    GNUNET_break (0);
-    GNUNET_free (plo->url);
-    GNUNET_free (plo);
-    return NULL;
+    eh = curl_easy_init ();
+    GNUNET_assert (NULL != eh);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_curl_easy_post (&och->post_ctx,
+                                         eh,
+                                         req_obj));
+    json_decref (req_obj);
+    GNUNET_assert (CURLE_OK ==
+                   curl_easy_setopt (eh,
+                                     CURLOPT_URL,
+                                     och->url));
+    och->job = GNUNET_CURL_job_add2 (ctx,
+                                     eh,
+                                     och->post_ctx.headers,
+                                     &handle_post_order_claim_finished,
+                                     och);
+    GNUNET_assert (NULL != och->job);
   }
-  return plo;
+  return och;
 }
 
 
 /**
- * Cancel a GET /proposal request.
+ * Cancel a POST /orders/$ID/claim request.
  *
- * @param plo handle to the request to be canceled
+ * @param och handle to the request to be canceled
  */
 void
-TALER_MERCHANT_proposal_lookup_cancel (
-  struct TALER_MERCHANT_ProposalLookupOperation *plo)
+TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle *och)
 {
-  if (NULL != plo->job)
+  if (NULL != och->job)
   {
-    GNUNET_CURL_job_cancel (plo->job);
-    plo->job = NULL;
+    GNUNET_CURL_job_cancel (och->job);
+    och->job = NULL;
   }
-  GNUNET_free (plo->url);
-  GNUNET_free (plo);
+  TALER_curl_easy_post_finished (&och->post_ctx);
+  GNUNET_free (och->url);
+  GNUNET_free (och);
 }
 
 
-/* end of merchant_api_proposal_lookup.c */
+/* end of merchant_api_post_order_claim.c */
diff --git a/src/lib/merchant_api_post_order_pay.c 
b/src/lib/merchant_api_post_order_pay.c
new file mode 100644
index 0000000..27f380f
--- /dev/null
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -0,0 +1,728 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014, 2015, 2016, 2017, 2020 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_post_order_pay.c
+ * @brief Implementation of the POST /order/$ID/pay request
+ *        of the merchant's HTTP API
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#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 <taler/taler_exchange_service.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * @brief A Pay Handle
+ */
+struct TALER_MERCHANT_OrderPayHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result in "pay" @e mode.
+   */
+  TALER_MERCHANT_OrderPayCallback pay_cb;
+
+  /**
+   * Closure for @a pay_cb.
+   */
+  void *pay_cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * The coins we are paying with.
+   */
+  struct TALER_MERCHANT_PaidCoin *coins;
+
+  /**
+   * Hash of the contract we are paying, set
+   * if @e am_wallet is true.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * Public key of the merchant (instance) being paid, set
+   * if @e am_wallet is true.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * Number of @e coins we are paying with.
+   */
+  unsigned int num_coins;
+
+  /**
+   * Set to true if this is the wallet API and we have
+   * initialized @e h_contract_terms and @e merchant_pub.
+   */
+  bool am_wallet;
+
+};
+
+
+/**
+ * We got a 403 response back from the exchange (or the merchant).
+ * Now we need to check the provided cryptograophic proof that the
+ * coin was actually already spent!
+ *
+ * @param pc handle of the original coin we paid with
+ * @param json cryptograophic proof of coin's transaction
+ *        history as was returned by the exchange/merchant
+ * @return #GNUNET_OK if proof checks out
+ */
+static int
+check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc,
+                    json_t *json)
+{
+  struct TALER_Amount spent;
+  struct TALER_Amount spent_plus_contrib;
+
+  if (GNUNET_OK !=
+      TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */
+                                          pc->amount_with_fee.currency,
+                                          &pc->coin_pub,
+                                          json,
+                                          &spent))
+  {
+    /* Exchange's history fails to verify */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (&spent_plus_contrib,
+                        &spent,
+                        &pc->amount_with_fee))
+  {
+    /* We got an integer overflow? Bad application! */
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (-1 != TALER_amount_cmp (&pc->denom_value,
+                              &spent_plus_contrib))
+  {
+    /* according to our calculations, the transaction should
+       have still worked, exchange error! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Accepting proof of double-spending\n");
+  return GNUNET_OK;
+}
+
+
+/**
+ * We got a 409 response back from the exchange (or the merchant).
+ * Now we need to check the provided cryptograophic proof that the
+ * coin was actually already spent!
+ *
+ * @param oph handle of the original pay operation
+ * @param json cryptograophic proof returned by the
+ *        exchange/merchant
+ * @return #GNUNET_OK if proof checks out
+ */
+static int
+check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
+                const json_t *json)
+{
+  json_t *history;
+  json_t *ereply;
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("exchange_reply", &ereply),
+    GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
+    GNUNET_JSON_spec_end ()
+  };
+  struct GNUNET_JSON_Specification hspec[] = {
+    GNUNET_JSON_spec_json ("history", &history),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (ereply,
+                         hspec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (spec);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_JSON_parse_free (spec);
+
+  for (unsigned int i = 0; i<oph->num_coins; i++)
+  {
+    if (0 == memcmp (&oph->coins[i].coin_pub,
+                     &coin_pub,
+                     sizeof (struct TALER_CoinSpendPublicKeyP)))
+    {
+      int ret;
+
+      ret = check_coin_history (&oph->coins[i],
+                                history);
+      GNUNET_JSON_parse_free (hspec);
+      return ret;
+    }
+  }
+  GNUNET_break_op (0); /* complaint is not about any of the coins
+                          that we actually paid with... */
+  GNUNET_JSON_parse_free (hspec);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /pay request.
+ *
+ * @param cls the `struct TALER_MERCHANT_Pay`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_pay_finished (void *cls,
+                     long response_code,
+                     const void *response)
+{
+  struct TALER_MERCHANT_OrderPayHandle *oph = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  oph->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "/pay completed with response code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (oph->am_wallet)
+    {
+      /* Here we can (and should) verify the merchant's signature */
+      struct PaymentResponsePS pr = {
+        .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+        .purpose.size = htonl (sizeof (pr)),
+        .h_contract_terms = oph->h_contract_terms
+      };
+      struct TALER_MerchantSignatureP merchant_sig;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("sig",
+                                     &merchant_sig),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        hr.http_status = 0;
+        hr.hint = "sig field missing in response";
+        break;
+      }
+
+      if (GNUNET_OK !=
+          GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+                                      &pr,
+                                      &merchant_sig.eddsa_sig,
+                                      &oph->merchant_pub.eddsa_pub))
+      {
+        GNUNET_break_op (0);
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        hr.http_status = 0;
+        hr.hint = "signature invalid";
+      }
+    }
+    break;
+  /* Tolerating Not Acceptable because sometimes
+   * - especially in tests - we might want to POST
+   * coins one at a time.  */
+  case MHD_HTTP_NOT_ACCEPTABLE:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* This should never happen, either us
+     * or the merchant is buggy (or API version conflict);
+     * just pass JSON reply to the application */
+    break;
+  case MHD_HTTP_PAYMENT_REQUIRED:
+    /* was originally paid, but then refunded */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, merchant says we tried to abort the payment
+     * after it was successful. We should pass the JSON reply to the
+     * application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the
+       application */
+    break;
+  case MHD_HTTP_PRECONDITION_FAILED:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* Nothing really to verify, the merchant is blaming us for failing to
+       satisfy some constraint (likely it does not like our exchange because
+       of some disagreement on the PKI).  We should pass the JSON reply to the
+       application */
+    break;
+  case MHD_HTTP_REQUEST_TIMEOUT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* The merchant couldn't generate a timely response, likely because
+       it itself waited too long on the exchange.
+       Pass on to application. */
+    break;
+  case MHD_HTTP_CONFLICT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    if (GNUNET_OK != check_conflict (oph,
+                                     json))
+    {
+      GNUNET_break_op (0);
+      response_code = 0;
+    }
+    break;
+  case MHD_HTTP_GONE:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* The merchant says we are too late, the offer has expired or some
+       denomination key of a coin involved has expired.
+       Might be a disagreement in timestamps? Still, pass on to application. */
+    break;
+  case MHD_HTTP_FAILED_DEPENDENCY:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* Nothing really to verify, the merchant is blaming the exchange.
+       We should pass the JSON reply to the application */
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Server had an internal issue; we should retry,
+       but this API leaves this to the application */
+    break;
+  case MHD_HTTP_SERVICE_UNAVAILABLE:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* Exchange couldn't respond properly; the retry is
+       left to the application */
+    break;
+  default:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  oph->pay_cb (oph->pay_cb_cls,
+               &hr);
+  TALER_MERCHANT_order_pay_cancel (oph);
+}
+
+
+/**
+ * PAY a merchant.  API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures.  Note the subtle difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ *
+ * @param ctx the execution loop context
+ * @param merchant_url base URL of the merchant's backend
+ * @param order_id order to pay
+ * @param session_id session to pay for, or NULL for none
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param pay_cb the callback to call when a reply for this request is 
available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay_frontend (
+  struct GNUNET_CURL_Context *ctx,
+  const char *merchant_url,
+  const char *order_id,
+  const char *session_id,
+  unsigned int num_coins,
+  const struct TALER_MERCHANT_PaidCoin coins[],
+  TALER_MERCHANT_OrderPayCallback pay_cb,
+  void *pay_cb_cls)
+{
+  struct TALER_MERCHANT_OrderPayHandle *oph;
+  json_t *pay_obj;
+  json_t *j_coins;
+  CURL *eh;
+  struct TALER_Amount total_fee;
+  struct TALER_Amount total_amount;
+
+  if (0 == num_coins)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  j_coins = json_array ();
+  for (unsigned int i = 0; i<num_coins; i++)
+  {
+    json_t *j_coin;
+    const struct TALER_MERCHANT_PaidCoin *pc = &coins[i];
+    struct TALER_Amount fee;
+    struct GNUNET_HashCode denom_hash;
+
+    if (0 >
+        TALER_amount_subtract (&fee,
+                               &pc->amount_with_fee,
+                               &pc->amount_without_fee))
+    {
+      /* Integer underflow, fee larger than total amount?
+         This should not happen (client violated API!) */
+      GNUNET_break (0);
+      json_decref (j_coins);
+      return NULL;
+    }
+    if (0 == i)
+    {
+      total_fee = fee;
+      total_amount = pc->amount_with_fee;
+    }
+    else
+    {
+      if ( (0 >
+            TALER_amount_add (&total_fee,
+                              &total_fee,
+                              &fee)) ||
+           (0 >
+            TALER_amount_add (&total_amount,
+                              &total_amount,
+                              &pc->amount_with_fee)) )
+      {
+        /* integer overflow */
+        GNUNET_break (0);
+        json_decref (j_coins);
+        return NULL;
+      }
+    }
+
+    GNUNET_CRYPTO_rsa_public_key_hash (pc->denom_pub.rsa_public_key,
+                                       &denom_hash);
+    /* create JSON for this coin */
+    j_coin = json_pack (
+      "{s:o, s:o,"                   /* contribution/coin_pub */
+      " s:s, s:o,"          /* exchange_url / denom_pub */
+      " s:o, s:o}",          /* ub_sig / coin_sig */
+      "contribution",
+      TALER_JSON_from_amount (&pc->amount_with_fee),
+      "coin_pub",
+      GNUNET_JSON_from_data_auto (&pc->coin_pub),
+      "exchange_url",
+      pc->exchange_url,
+      "h_denom",
+      GNUNET_JSON_from_data_auto (&denom_hash),
+      "ub_sig",
+      GNUNET_JSON_from_rsa_signature (pc->denom_sig.rsa_signature),
+      "coin_sig",
+      GNUNET_JSON_from_data_auto (&pc->coin_sig));
+    if (0 !=
+        json_array_append_new (j_coins,
+                               j_coin))
+    {
+      GNUNET_break (0);
+      json_decref (j_coins);
+      return NULL;
+    }
+  }
+
+  pay_obj = json_pack ("{ s:o }",
+                       "coins",
+                       j_coins);
+  if (NULL == pay_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  if (NULL != session_id)
+  {
+    if (0 != json_object_set (pay_obj,
+                              "session_id",
+                              json_string (session_id)))
+    {
+      GNUNET_break (0);
+      json_decref (pay_obj);
+      return NULL;
+    }
+  }
+
+  oph = GNUNET_new (struct TALER_MERCHANT_OrderPayHandle);
+  oph->ctx = ctx;
+  oph->pay_cb = pay_cb;
+  oph->pay_cb_cls = pay_cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "orders/%s/pay",
+                     order_id);
+    oph->url = TALER_url_join (merchant_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == oph->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    json_decref (pay_obj);
+    GNUNET_free (oph);
+    return NULL;
+  }
+  oph->num_coins = num_coins;
+  oph->coins = GNUNET_new_array (num_coins,
+                                 struct TALER_MERCHANT_PaidCoin);
+  memcpy (oph->coins,
+          coins,
+          num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
+
+  eh = curl_easy_init ();
+  if (GNUNET_OK !=
+      TALER_curl_easy_post (&oph->post_ctx,
+                            eh,
+                            pay_obj))
+  {
+    GNUNET_break (0);
+    json_decref (pay_obj);
+    GNUNET_free (oph);
+    return NULL;
+  }
+
+  json_decref (pay_obj);
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   oph->url));
+  oph->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   oph->post_ctx.headers,
+                                   &handle_pay_finished,
+                                   oph);
+  return oph;
+}
+
+
+/**
+ * Pay a merchant.  API for wallets that have the coin's private keys.
+ * _NOTE_: this function does NOT calculate each coin amount in order
+ * to match the contract total price.  This calculation is to be made
+ * by the logic using this library.
+ *
+ * @param ctx the execution loop context
+ * @param merchant_url base URL of the merchant's backend
+ * @param session_id session to pay for, or NULL for none
+ * @param h_contract_terms hashcode of the proposal being paid
+ * @param amount total value of the contract to be paid to the merchant
+ * @param max_fee maximum fee covered by the merchant (according to the 
contract)
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param merchant_sig signature from the merchant over the original contract
+ * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
+ * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
+ * @param pay_deadline maximum time limit to pay for this contract
+ * @param h_wire hash of the merchant’s account details
+ * @param order_id order id of the proposal being paid
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param pay_cb the callback to call when a reply for this request is 
available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay (struct GNUNET_CURL_Context *ctx,
+                          const char *merchant_url,
+                          const char *session_id,
+                          const struct GNUNET_HashCode *h_contract_terms,
+                          const struct TALER_Amount *amount,
+                          const struct TALER_Amount *max_fee,
+                          const struct TALER_MerchantPublicKeyP *merchant_pub,
+                          const struct TALER_MerchantSignatureP *merchant_sig,
+                          struct GNUNET_TIME_Absolute timestamp,
+                          struct GNUNET_TIME_Absolute refund_deadline,
+                          struct GNUNET_TIME_Absolute pay_deadline,
+                          const struct GNUNET_HashCode *h_wire,
+                          const char *order_id,
+                          unsigned int num_coins,
+                          const struct TALER_MERCHANT_PayCoin coins[],
+                          TALER_MERCHANT_OrderPayCallback pay_cb,
+                          void *pay_cb_cls)
+{
+  (void) GNUNET_TIME_round_abs (&timestamp);
+  (void) GNUNET_TIME_round_abs (&pay_deadline);
+  (void) GNUNET_TIME_round_abs (&refund_deadline);
+  if (GNUNET_YES !=
+      TALER_amount_cmp_currency (amount,
+                                 max_fee))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+
+  {
+    struct TALER_MERCHANT_PaidCoin pc[num_coins];
+    struct TALER_DepositRequestPS dr = {
+      .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
+      .purpose.size = htonl (sizeof (dr)),
+      .h_contract_terms = *h_contract_terms,
+      .h_wire = *h_wire,
+      .wallet_timestamp = GNUNET_TIME_absolute_hton (timestamp),
+      .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline),
+      .merchant = *merchant_pub
+    };
+
+    for (unsigned int i = 0; i<num_coins; i++)
+    {
+      const struct TALER_MERCHANT_PayCoin *coin = &coins[i]; // coin priv.
+      struct TALER_MERCHANT_PaidCoin *p = &pc[i]; // coin pub.
+      struct TALER_Amount fee;
+
+      /* prepare 'dr' for this coin to generate coin signature */
+      GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv,
+                                          &dr.coin_pub.eddsa_pub);
+      TALER_amount_hton (&dr.amount_with_fee,
+                         &coin->amount_with_fee);
+      if (0 >
+          TALER_amount_subtract (&fee,
+                                 &coin->amount_with_fee,
+                                 &coin->amount_without_fee))
+      {
+        /* Integer underflow, fee larger than total amount?
+           This should not happen (client violated API!) */
+        GNUNET_break (0);
+        return NULL;
+      }
+      TALER_amount_hton (&dr.deposit_fee,
+                         &fee);
+      GNUNET_CRYPTO_eddsa_sign (&coin->coin_priv.eddsa_priv,
+                                &dr,
+                                &p->coin_sig.eddsa_signature);
+      p->denom_pub = coin->denom_pub;
+      p->denom_sig = coin->denom_sig;
+      p->denom_value = coin->denom_value;
+      p->coin_pub = dr.coin_pub;
+      p->amount_with_fee = coin->amount_with_fee;
+      p->amount_without_fee = coin->amount_without_fee;
+      p->exchange_url = coin->exchange_url;
+    }
+    {
+      struct TALER_MERCHANT_OrderPayHandle *oph;
+
+      oph = TALER_MERCHANT_order_pay_frontend (ctx,
+                                               merchant_url,
+                                               order_id,
+                                               session_id,
+                                               num_coins,
+                                               pc,
+                                               pay_cb,
+                                               pay_cb_cls);
+      if (NULL == oph)
+        return NULL;
+      oph->h_contract_terms = *h_contract_terms;
+      oph->merchant_pub = *merchant_pub;
+      oph->am_wallet = true;
+      return oph;
+    }
+  }
+}
+
+
+/**
+ * Cancel a pay request.  This function cannot be used on a request handle if
+ * a response is already served for it.
+ *
+ * @param oph the pay request handle
+ */
+void
+TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph)
+{
+  if (NULL != oph->job)
+  {
+    GNUNET_CURL_job_cancel (oph->job);
+    oph->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&oph->post_ctx);
+  GNUNET_free (oph->coins);
+  GNUNET_free (oph->url);
+  GNUNET_free (oph);
+}
+
+
+/* end of merchant_api_post_order_pay.c */
diff --git a/src/lib/merchant_api_refund_increase.c 
b/src/lib/merchant_api_post_order_refund.c
similarity index 53%
rename from src/lib/merchant_api_refund_increase.c
rename to src/lib/merchant_api_post_order_refund.c
index 619d2b8..fb93864 100644
--- a/src/lib/merchant_api_refund_increase.c
+++ b/src/lib/merchant_api_post_order_refund.c
@@ -15,8 +15,8 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/merchant_api_refund_increase.c
- * @brief Implementation of the /refund POST and GET
+ * @file lib/merchant_api_post_order_refund.c
+ * @brief Implementation of the POST /orders/ID/refund request
  * @author Christian Grothoff
  * @author Marcello Stanisci
  */
@@ -32,7 +32,10 @@
 #include <taler/taler_curl_lib.h>
 
 
-struct TALER_MERCHANT_RefundIncreaseOperation
+/**
+ * Handle for a POST /orders/ID/refund operation.
+ */
+struct TALER_MERCHANT_OrderRefundHandle
 {
   /**
    * Complete URL where the backend offers /refund
@@ -52,7 +55,7 @@ struct TALER_MERCHANT_RefundIncreaseOperation
   /**
    * The callback to pass the backend response to
    */
-  TALER_MERCHANT_RefundIncreaseCallback cb;
+  TALER_MERCHANT_RefundCallback cb;
 
   /**
    * Clasure to pass to the callback
@@ -67,41 +70,41 @@ struct TALER_MERCHANT_RefundIncreaseOperation
 
 
 /**
- * Callback to process POST /refund response
+ * Callback to process POST /orders/ID/refund response
  *
- * @param cls the `struct TALER_MERCHANT_RefundIncreaseOperation`
+ * @param cls the `struct TALER_MERCHANT_OrderRefundHandle`
  * @param response_code HTTP response code, 0 on error
  * @param json response body, NULL if not JSON
  */
 static void
-handle_refund_increase_finished (void *cls,
-                                 long response_code,
-                                 const void *response)
+handle_refund_finished (void *cls,
+                        long response_code,
+                        const void *response)
 {
-  struct TALER_MERCHANT_RefundIncreaseOperation *rio = cls;
+  struct TALER_MERCHANT_OrderRefundHandle *orh = cls;
   const json_t *json = response;
   struct TALER_MERCHANT_HttpResponse hr = {
     .http_status = (unsigned int) response_code,
     .reply = json
   };
 
-  rio->job = NULL;
+  orh->job = NULL;
   switch (response_code)
   {
   case 0:
     hr.ec = TALER_EC_INVALID_RESPONSE;
-    rio->cb (rio->cb_cls,
+    orh->cb (orh->cb_cls,
              &hr);
     break;
   case MHD_HTTP_OK:
-    rio->cb (rio->cb_cls,
+    orh->cb (orh->cb_cls,
              &hr);
     break;
   case MHD_HTTP_CONFLICT:
   case MHD_HTTP_NOT_FOUND:
     hr.ec = TALER_JSON_get_error_code (json);
     hr.hint = TALER_JSON_get_error_hint (json);
-    rio->cb (rio->cb_cls,
+    orh->cb (orh->cb_cls,
              &hr);
     break;
   default:
@@ -109,36 +112,36 @@ handle_refund_increase_finished (void *cls,
     TALER_MERCHANT_parse_error_details_ (json,
                                          response_code,
                                          &hr);
-    rio->cb (rio->cb_cls,
+    orh->cb (orh->cb_cls,
              &hr);
     break;
   }
-  TALER_MERCHANT_refund_increase_cancel (rio);
+  TALER_MERCHANT_post_order_refund_cancel (orh);
 }
 
 
 /**
- * Cancel a POST /refund request.
+ * Cancel a POST /orders/ID/refund request.
  *
- * @param rio the refund increasing operation to cancel
+ * @param orh the refund increasing operation to cancel
  */
 void
-TALER_MERCHANT_refund_increase_cancel (
-  struct TALER_MERCHANT_RefundIncreaseOperation *rio)
+TALER_MERCHANT_post_order_refund_cancel (
+  struct TALER_MERCHANT_OrderRefundHandle *orh)
 {
-  if (NULL != rio->job)
+  if (NULL != orh->job)
   {
-    GNUNET_CURL_job_cancel (rio->job);
-    rio->job = NULL;
+    GNUNET_CURL_job_cancel (orh->job);
+    orh->job = NULL;
   }
-  TALER_curl_easy_post_finished (&rio->post_ctx);
-  GNUNET_free (rio->url);
-  GNUNET_free (rio);
+  TALER_curl_easy_post_finished (&orh->post_ctx);
+  GNUNET_free (orh->url);
+  GNUNET_free (orh);
 }
 
 
 /**
- * Increase the refund associated to a order
+ * Increase the refund granted for an order
  *
  * @param ctx the CURL context used to connect to the backend
  * @param backend_url backend's base URL, including final "/"
@@ -148,65 +151,76 @@ TALER_MERCHANT_refund_increase_cancel (
  * @param cb callback processing the response from /refund
  * @param cb_cls closure for cb
  */
-struct TALER_MERCHANT_RefundIncreaseOperation *
-TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
-                                const char *backend_url,
-                                const char *order_id,
-                                const struct TALER_Amount *refund,
-                                const char *reason,
-                                TALER_MERCHANT_RefundIncreaseCallback cb,
-                                void *cb_cls)
+struct TALER_MERCHANT_OrderRefundHandle *
+TALER_MERCHANT_post_order_refund (struct GNUNET_CURL_Context *ctx,
+                                  const char *backend_url,
+                                  const char *order_id,
+                                  const struct TALER_Amount *refund,
+                                  const char *reason,
+                                  TALER_MERCHANT_RefundCallback cb,
+                                  void *cb_cls)
 {
-  struct TALER_MERCHANT_RefundIncreaseOperation *rio;
+  struct TALER_MERCHANT_OrderRefundHandle *orh;
   json_t *req;
   CURL *eh;
 
-  rio = GNUNET_new (struct TALER_MERCHANT_RefundIncreaseOperation);
-  rio->ctx = ctx;
-  rio->cb = cb;
-  rio->cb_cls = cb_cls;
-  rio->url = TALER_url_join (backend_url,
-                             "refund",
-                             NULL);
-  if (NULL == rio->url)
+  orh = GNUNET_new (struct TALER_MERCHANT_OrderRefundHandle);
+  orh->ctx = ctx;
+  orh->cb = cb;
+  orh->cb_cls = cb_cls;
+  {
+    char *path;
+
+    GNUNET_asprintf (&path,
+                     "private/orders/%s/refund",
+                     order_id);
+    orh->url = TALER_url_join (backend_url,
+                               path,
+                               NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == orh->url)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Could not construct request URL.\n");
-    GNUNET_free (rio);
+    GNUNET_free (orh);
     return NULL;
   }
-
-  req = json_pack ("{s:o, s:s, s:s}",
+  req = json_pack ("{s:o, s:s}",
                    "refund", TALER_JSON_from_amount (refund),
-                   "order_id", order_id,
                    "reason", reason);
+  GNUNET_assert (NULL != req);
   eh = curl_easy_init ();
-
-  if (GNUNET_OK != TALER_curl_easy_post (&rio->post_ctx,
-                                         eh,
-                                         req))
+  GNUNET_assert (NULL != eh);
+  if (GNUNET_OK !=
+      TALER_curl_easy_post (&orh->post_ctx,
+                            eh,
+                            req))
   {
     GNUNET_break (0);
     json_decref (req);
-    GNUNET_free (rio->url);
-    GNUNET_free (rio);
+    GNUNET_free (orh->url);
+    GNUNET_free (orh);
     return NULL;
   }
   json_decref (req);
-  GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
-                                               CURLOPT_URL,
-                                               rio->url));
-  rio->job = GNUNET_CURL_job_add2
-               (ctx,
-               eh,
-               rio->post_ctx.headers,
-               &handle_refund_increase_finished,
-               rio);
-  if (NULL == rio->job)
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   orh->url));
+  orh->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   orh->post_ctx.headers,
+                                   &handle_refund_finished,
+                                   orh);
+  if (NULL == orh->job)
   {
-    GNUNET_free (rio->url);
-    GNUNET_free (rio);
+    GNUNET_free (orh->url);
+    GNUNET_free (orh);
     return NULL;
   }
-  return rio;
+  return orh;
 }
+
+
+/* end of merchant_api_post_order_refund.c */
diff --git a/src/lib/merchant_api_proposal.c 
b/src/lib/merchant_api_post_orders.c
similarity index 50%
rename from src/lib/merchant_api_proposal.c
rename to src/lib/merchant_api_post_orders.c
index e5e155f..73815fd 100644
--- a/src/lib/merchant_api_proposal.c
+++ b/src/lib/merchant_api_post_orders.c
@@ -17,8 +17,8 @@
   see <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/merchant_api_proposal.c
- * @brief Implementation of the /proposal POST
+ * @file lib/merchant_api_post_orders.c
+ * @brief Implementation of the POST /orders
  * @author Christian Grothoff
  * @author Marcello Stanisci
  */
@@ -35,9 +35,9 @@
 
 
 /**
- * @brief A Contract Operation Handle
+ * @brief A POST /orders Handle
  */
-struct TALER_MERCHANT_ProposalOperation
+struct TALER_MERCHANT_PostOrdersOperation
 {
 
   /**
@@ -53,7 +53,7 @@ struct TALER_MERCHANT_ProposalOperation
   /**
    * Function to call with the result.
    */
-  TALER_MERCHANT_ProposalCallback cb;
+  TALER_MERCHANT_PostOrdersCallback cb;
 
   /**
    * Closure for @a cb.
@@ -74,18 +74,18 @@ struct TALER_MERCHANT_ProposalOperation
 
 /**
  * Function called when we're done processing the
- * HTTP POST /proposal request.
+ * HTTP POST /orders request.
  *
- * @param cls the `struct TALER_MERCHANT_ProposalOperation`
+ * @param cls the `struct TALER_MERCHANT_PostOrdersOperation`
  * @param response_code HTTP response code, 0 on error
  * @param json response body, NULL if not JSON
  */
 static void
-handle_proposal_finished (void *cls,
-                          long response_code,
-                          const void *response)
+handle_post_order_finished (void *cls,
+                            long response_code,
+                            const void *response)
 {
-  struct TALER_MERCHANT_ProposalOperation *po = cls;
+  struct TALER_MERCHANT_PostOrdersOperation *po = cls;
   const char *order_id = NULL;
   const json_t *json = response;
   struct TALER_MERCHANT_HttpResponse hr = {
@@ -162,7 +162,7 @@ handle_proposal_finished (void *cls,
           order_id);
   if (MHD_HTTP_OK == response_code)
     GNUNET_JSON_parse_free (spec);
-  TALER_MERCHANT_proposal_cancel (po);
+  TALER_MERCHANT_orders_post_cancel (po);
 }
 
 
@@ -173,30 +173,143 @@ handle_proposal_finished (void *cls,
  * @param backend_url URL of the backend
  * @param order basic information about this purchase,
  *        to be extended by the backend
- * @param proposal_cb the callback to call when a reply
+ * @param refund_delay how long can refunds happen for this order; 0 to use
+ *             absolute value from contract (or not allow refunds).
+ * @param cb the callback to call when a reply
  *        for this request is available
- * @param proposal_cb_cls closure for @a proposal_cb
+ * @param cb_cls closure for @a proposal_cb
  * @return a handle for this request, NULL on error
  */
-struct TALER_MERCHANT_ProposalOperation *
-TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx,
-                          const char *backend_url,
-                          const json_t *order,
-                          TALER_MERCHANT_ProposalCallback proposal_cb,
-                          void *proposal_cb_cls)
+struct TALER_MERCHANT_PostOrdersOperation *
+TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx,
+                            const char *backend_url,
+                            const json_t *order,
+                            struct GNUNET_TIME_Relative refund_delay,
+                            TALER_MERCHANT_PostOrdersCallback cb,
+                            void *cb_cls)
 {
-  struct TALER_MERCHANT_ProposalOperation *po;
+  return TALER_MERCHANT_orders_post2 (ctx,
+                                      backend_url,
+                                      order,
+                                      refund_delay,
+                                      NULL,
+                                      0,
+                                      NULL,
+                                      0,
+                                      NULL,
+                                      cb,
+                                      cb_cls);
+}
+
+
+/**
+ * POST to /orders at the backend to setup an order and obtain
+ * the order ID (which may have been set by the front-end).
+ *
+ * @param ctx execution context
+ * @param backend_url URL of the backend
+ * @param order basic information about this purchase, to be extended by the 
backend
+ * @param refund_delay how long can refunds happen for this order; 0 to use
+ *             absolute value from contract (or not allow refunds).
+ * @param payment_target desired payment target identifier (to select merchant 
bank details)
+ * @param inventory_products_length length of the @a inventory_products array
+ * @param inventory_products products to add to the order from the inventory
+ * @param lock_uuids_length length of the @a uuids array
+ * @param uuids array of UUIDs with locks on @a inventory_products
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request, NULL on error
+ */
+struct TALER_MERCHANT_PostOrdersOperation *
+TALER_MERCHANT_orders_post2 (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const json_t *order,
+  struct GNUNET_TIME_Relative refund_delay,
+  const char *payment_target,
+  unsigned int inventory_products_length,
+  const struct TALER_MERCHANT_InventoryProduct inventory_products[],
+  unsigned int uuids_length,
+  const struct GNUNET_Uuid uuids[],
+  TALER_MERCHANT_PostOrdersCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_PostOrdersOperation *po;
   json_t *req;
   CURL *eh;
+  const char *delay_s;
 
-  po = GNUNET_new (struct TALER_MERCHANT_ProposalOperation);
+  delay_s = GNUNET_STRINGS_relative_time_to_string (refund_delay,
+                                                    GNUNET_NO);
+  po = GNUNET_new (struct TALER_MERCHANT_PostOrdersOperation);
   po->ctx = ctx;
-  po->cb = proposal_cb;
-  po->cb_cls = proposal_cb_cls;
-  po->url = TALER_url_join (backend_url, "order", NULL);
+  po->cb = cb;
+  po->cb_cls = cb_cls;
+  po->url = TALER_url_join (backend_url,
+                            "private/orders",
+                            "refund_delay",
+                            (0 != refund_delay.rel_value_us)
+                            ? delay_s
+                            : NULL,
+                            NULL);
   req = json_pack ("{s:O}",
                    "order", (json_t *) order);
+  GNUNET_assert (NULL != req);
+  if (NULL != payment_target)
+  {
+    GNUNET_assert (0 ==
+                   json_object_set_new (req,
+                                        "payment_target",
+                                        json_string (payment_target)));
+  }
+  if (0 != inventory_products_length)
+  {
+    json_t *ipa = json_array ();
+
+    GNUNET_assert (NULL != ipa);
+    for (unsigned int i = 0; i<inventory_products_length; i++)
+    {
+      json_t *ip;
+
+      ip = json_pack ("{s:s, s:I}",
+                      "product_id",
+                      inventory_products[i].product_id,
+                      "quantity",
+                      (json_int_t) inventory_products[i].quantity);
+      GNUNET_assert (NULL != ip);
+      GNUNET_assert (0 ==
+                     json_array_append_new (ipa,
+                                            ip));
+    }
+    GNUNET_assert (0 ==
+                   json_object_set_new (req,
+                                        "inventory_products",
+                                        ipa));
+  }
+  if (0 != uuids_length)
+  {
+    json_t *ua = json_array ();
+
+    GNUNET_assert (NULL != ua);
+    for (unsigned int i = 0; i<uuids_length; i++)
+    {
+      json_t *u;
+
+      u = json_pack ("{s:o}",
+                     "uuid",
+                     GNUNET_JSON_from_data_auto (&uuids[i]));
+      GNUNET_assert (NULL != u);
+      GNUNET_assert (0 ==
+                     json_array_append_new (ua,
+                                            u));
+    }
+    GNUNET_assert (0 ==
+                   json_object_set_new (req,
+                                        "lock_uuids",
+                                        ua));
+  }
   eh = curl_easy_init ();
+  GNUNET_assert (NULL != eh);
   if (GNUNET_OK != TALER_curl_easy_post (&po->post_ctx,
                                          eh,
                                          req))
@@ -207,7 +320,6 @@ TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx,
     return NULL;
   }
   json_decref (req);
-
   GNUNET_assert (CURLE_OK ==
                  curl_easy_setopt (eh,
                                    CURLOPT_URL,
@@ -215,7 +327,7 @@ TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx,
   po->job = GNUNET_CURL_job_add2 (ctx,
                                   eh,
                                   po->post_ctx.headers,
-                                  &handle_proposal_finished,
+                                  &handle_post_order_finished,
                                   po);
   return po;
 }
@@ -228,7 +340,8 @@ TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx,
  * @param po the proposal operation request handle
  */
 void
-TALER_MERCHANT_proposal_cancel (struct TALER_MERCHANT_ProposalOperation *po)
+TALER_MERCHANT_orders_post_cancel (
+  struct TALER_MERCHANT_PostOrdersOperation *po)
 {
   if (NULL != po->job)
   {
@@ -241,4 +354,4 @@ TALER_MERCHANT_proposal_cancel (struct 
TALER_MERCHANT_ProposalOperation *po)
 }
 
 
-/* end of merchant_api_proposal.c */
+/* end of merchant_api_post_orders.c */
diff --git a/src/lib/merchant_api_post_products.c 
b/src/lib/merchant_api_post_products.c
new file mode 100644
index 0000000..f354fb5
--- /dev/null
+++ b/src/lib/merchant_api_post_products.c
@@ -0,0 +1,287 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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_post_products.c
+ * @brief Implementation of the POST /products request
+ *        of the merchant's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+
+
+/**
+ * Handle for a POST /products/$ID operation.
+ */
+struct TALER_MERCHANT_ProductsPostHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_ProductsPostCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /products request.
+ *
+ * @param cls the `struct TALER_MERCHANT_ProductsPostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_post_products_finished (void *cls,
+                               long response_code,
+                               const void *response)
+{
+  struct TALER_MERCHANT_ProductsPostHandle *pph = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  pph->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "POST /products completed with response code %u\n",
+              (unsigned int) response_code);
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* This should never happen, either us
+     * or the merchant is buggy (or API version conflict);
+     * just pass JSON reply to the application */
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, merchant says we tried to abort the payment
+     * after it was successful. We should pass the JSON reply to the
+     * application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the
+       application */
+    break;
+  case MHD_HTTP_CONFLICT:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    /* Server had an internal issue; we should retry,
+       but this API leaves this to the application */
+    break;
+  default:
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    /* unexpected response code */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  pph->cb (pph->cb_cls,
+           &hr);
+  TALER_MERCHANT_products_post_cancel (pph);
+}
+
+
+/**
+ * Make a POST /products request to add a product to the
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier to use for the product
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books)
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductsPostHandle *
+TALER_MERCHANT_products_post (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const char *product_id,
+  const char *description,
+  const json_t *description_i18n,
+  const char *unit,
+  const struct TALER_Amount *price,
+  const json_t *image,
+  const json_t *taxes,
+  int64_t total_stock,
+  const json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  TALER_MERCHANT_ProductsPostCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_ProductsPostHandle *pph;
+  json_t *req_obj;
+
+  (void) GNUNET_TIME_round_abs (&next_restock);
+  req_obj = json_pack ("{s:s, s:s, s:O, s:s, s:o,"
+                       " s:O, s:O, s:I, s:O, s:o}",
+                       "product_id",
+                       product_id,
+                       "description",
+                       description,
+                       "description_i18n",
+                       description_i18n,
+                       "unit",
+                       unit,
+                       "price",
+                       TALER_JSON_from_amount (price),
+                       /* End of first group of 5 */
+                       "image",
+                       image,
+                       "taxes",
+                       taxes,
+                       "total_stock",
+                       (json_int_t) total_stock,
+                       "address",
+                       address,
+                       "next_restock",
+                       GNUNET_JSON_from_time_abs (next_restock));
+  if (NULL == req_obj)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  pph = GNUNET_new (struct TALER_MERCHANT_ProductsPostHandle);
+  pph->ctx = ctx;
+  pph->cb = cb;
+  pph->cb_cls = cb_cls;
+  pph->url = TALER_url_join (backend_url,
+                             "private/products",
+                             NULL);
+  if (NULL == pph->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    json_decref (req_obj);
+    GNUNET_free (pph);
+    return NULL;
+  }
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    GNUNET_assert (NULL != eh);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_curl_easy_post (&pph->post_ctx,
+                                         eh,
+                                         req_obj));
+    json_decref (req_obj);
+    GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
+                                                 CURLOPT_URL,
+                                                 pph->url));
+    pph->job = GNUNET_CURL_job_add2 (ctx,
+                                     eh,
+                                     pph->post_ctx.headers,
+                                     &handle_post_products_finished,
+                                     pph);
+    GNUNET_assert (NULL != pph->job);
+  }
+  return pph;
+}
+
+
+/**
+ * Cancel /products request.  Must not be called by clients after
+ * the callback was invoked.
+ *
+ * @param igh request to cancel.
+ */
+void
+TALER_MERCHANT_products_post_cancel (
+  struct TALER_MERCHANT_ProductsPostHandle *pph)
+{
+  if (NULL != pph->job)
+  {
+    GNUNET_CURL_job_cancel (pph->job);
+    pph->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&pph->post_ctx);
+  GNUNET_free (pph->url);
+  GNUNET_free (pph);
+}
+
+
+/* end of merchant_api_post_products.c */
diff --git a/src/lib/merchant_api_post_reserves.c 
b/src/lib/merchant_api_post_reserves.c
new file mode 100644
index 0000000..9465635
--- /dev/null
+++ b/src/lib/merchant_api_post_reserves.c
@@ -0,0 +1,258 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014, 2015, 2016, 2020 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_post_reserves.c
+ * @brief Implementation of the POST /reserves 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 "taler_merchant_service.h"
+#include <taler/taler_curl_lib.h>
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * @brief A handle for POSTing reserve data.
+ */
+struct TALER_MERCHANT_PostReservesHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_PostReservesCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /reserves request.
+ *
+ * @param cls the `struct TALER_MERCHANT_PostReservesHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_post_reserves_finished (void *cls,
+                               long response_code,
+                               const void *response)
+{
+  struct TALER_MERCHANT_PostReservesHandle *prh = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  prh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      const char *payto_uri;
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                     &reserve_pub),
+        GNUNET_JSON_spec_string ("payto_uri",
+                                 &payto_uri),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        break;
+      }
+      else
+      {
+        prh->cb (prh->cb_cls,
+                 &hr,
+                 &reserve_pub,
+                 payto_uri);
+        GNUNET_JSON_parse_free (spec);
+        TALER_MERCHANT_reserves_post_cancel (prh);
+        return;
+      }
+    }
+  case MHD_HTTP_ACCEPTED:
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Did not find any data\n");
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  prh->cb (prh->cb_cls,
+           &hr,
+           NULL,
+           NULL);
+  TALER_MERCHANT_reserves_post_cancel (prh);
+}
+
+
+/**
+ * Request backend to create a reserve.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the backend
+ * @param initial_balance desired initial balance for the reserve
+ * @param exchange_url what is the URL of the exchange where the reserve 
should be set up
+ * @param wire_method desired wire method, for example "iban" or "x-taler-bank"
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_PostReservesHandle *
+TALER_MERCHANT_reserves_post (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const struct TALER_Amount *initial_balance,
+  const char *exchange_url,
+  const char *wire_method,
+  TALER_MERCHANT_PostReservesCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_PostReservesHandle *prh;
+  CURL *eh;
+  json_t *req;
+
+  prh = GNUNET_new (struct TALER_MERCHANT_PostReservesHandle);
+  prh->ctx = ctx;
+  prh->cb = cb;
+  prh->cb_cls = cb_cls;
+  prh->url = TALER_url_join (backend_url,
+                             "private/reserves",
+                             NULL);
+  if (NULL == prh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (prh);
+    return NULL;
+  }
+  req = json_pack ("{s:o, s:s, s:s}",
+                   "initial_balance", TALER_JSON_from_amount (initial_balance),
+                   "wire_method", wire_method,
+                   "exchange_url", exchange_url);
+  GNUNET_assert (NULL != req);
+  eh = curl_easy_init ();
+  GNUNET_assert (NULL != eh);
+  if (GNUNET_OK != TALER_curl_easy_post (&prh->post_ctx,
+                                         eh,
+                                         req))
+  {
+    GNUNET_break (0);
+    json_decref (req);
+    GNUNET_free (prh);
+    return NULL;
+  }
+  json_decref (req);
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   prh->url));
+  prh->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   prh->post_ctx.headers,
+                                   &handle_post_reserves_finished,
+                                   prh);
+  return prh;
+}
+
+
+/**
+ * Cancel a POST /reserves request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param prh handle to the tracking operation being cancelled
+ */
+void
+TALER_MERCHANT_reserves_post_cancel (
+  struct TALER_MERCHANT_PostReservesHandle *prh)
+{
+  if (NULL != prh->job)
+  {
+    GNUNET_CURL_job_cancel (prh->job);
+    prh->job = NULL;
+  }
+  GNUNET_free (prh->url);
+  TALER_curl_easy_post_finished (&prh->post_ctx);
+  GNUNET_free (prh);
+}
+
+
+/* end of merchant_api_post_reserves.c */
diff --git a/src/lib/merchant_api_post_transfers.c 
b/src/lib/merchant_api_post_transfers.c
new file mode 100644
index 0000000..f9ea25d
--- /dev/null
+++ b/src/lib/merchant_api_post_transfers.c
@@ -0,0 +1,326 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014, 2015, 2016, 2020 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_post_transfers.c
+ * @brief Implementation of the POST /transfers 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 "taler_merchant_service.h"
+#include <taler/taler_curl_lib.h>
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * @brief A handle for POSTing transfer data.
+ */
+struct TALER_MERCHANT_PostTransfersHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_PostTransfersCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /transfers request.
+ *
+ * @param cls the `struct TALER_MERCHANT_PostTransfersHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, NULL if not in JSON
+ */
+static void
+handle_post_transfers_finished (void *cls,
+                                long response_code,
+                                const void *response)
+{
+  struct TALER_MERCHANT_PostTransfersHandle *pth = cls;
+  const json_t *json = response;
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .http_status = (unsigned int) response_code,
+    .reply = json
+  };
+
+  pth->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      struct TALER_Amount total;
+      struct TALER_Amount wire_fee;
+      struct GNUNET_TIME_Absolute execution_time;
+      json_t *deposit_sums;
+      struct GNUNET_JSON_Specification spec[] = {
+        TALER_JSON_spec_amount ("total",
+                                &total),
+        TALER_JSON_spec_amount ("wire_fee",
+                                &wire_fee),
+        GNUNET_JSON_spec_absolute_time ("execution_time",
+                                        &execution_time),
+        GNUNET_JSON_spec_json ("deposit_sums",
+                               &deposit_sums),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (json,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr.http_status = 0;
+        hr.ec = TALER_EC_INVALID_RESPONSE;
+        break;
+      }
+      else
+      {
+        size_t deposit_sums_length;
+        struct TALER_MERCHANT_TrackTransferDetail *details;
+        json_t *deposit_sum;
+        unsigned int i;
+        bool ok;
+
+        if (! json_is_array (deposit_sums))
+        {
+          GNUNET_break_op (0);
+          GNUNET_JSON_parse_free (spec);
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+          break;
+        }
+        deposit_sums_length = json_array_size (deposit_sums);
+        details = GNUNET_new_array (deposit_sums_length,
+                                    struct TALER_MERCHANT_TrackTransferDetail);
+        ok = true;
+        json_array_foreach (deposit_sums, i, deposit_sum) {
+          struct TALER_MERCHANT_TrackTransferDetail *d = &details[i];
+          struct GNUNET_JSON_Specification ispec[] = {
+            GNUNET_JSON_spec_string ("order_id",
+                                     &d->order_id),
+            TALER_JSON_spec_amount ("deposit_value",
+                                    &d->deposit_value),
+            TALER_JSON_spec_amount ("deposit_fee",
+                                    &d->deposit_fee),
+            GNUNET_JSON_spec_end ()
+          };
+
+          if (GNUNET_OK !=
+              GNUNET_JSON_parse (deposit_sum,
+                                 ispec,
+                                 NULL, NULL))
+          {
+            GNUNET_break_op (0);
+            ok = false;
+            break;
+          }
+        }
+
+        if (! ok)
+        {
+          GNUNET_break_op (0);
+          GNUNET_free (details);
+          GNUNET_JSON_parse_free (spec);
+          hr.http_status = 0;
+          hr.ec = TALER_EC_INVALID_RESPONSE;
+          break;
+        }
+        pth->cb (pth->cb_cls,
+                 &hr,
+                 execution_time,
+                 &total,
+                 &wire_fee,
+                 deposit_sums_length,
+                 details);
+        GNUNET_free (details);
+        GNUNET_JSON_parse_free (spec);
+        TALER_MERCHANT_transfers_post_cancel (pth);
+        return;
+      }
+    }
+  case MHD_HTTP_ACCEPTED:
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Did not find any data\n");
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    hr.ec = TALER_JSON_get_error_code (json);
+    hr.hint = TALER_JSON_get_error_hint (json);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    TALER_MERCHANT_parse_error_details_ (json,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  pth->cb (pth->cb_cls,
+           &hr,
+           GNUNET_TIME_UNIT_FOREVER_ABS,
+           NULL,
+           NULL,
+           0,
+           NULL);
+  TALER_MERCHANT_transfers_post_cancel (pth);
+}
+
+
+/**
+ * Request backend to remember that we received the given
+ * wire transfer and to request details about the aggregated
+ * transactions from the exchange.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the backend
+ * @param credit_amount how much money did we receive (without wire fee)
+ * @param wtid base32 string indicating a wtid
+ * @param payto_uri which account was credited by the wire transfer
+ * @param exchange_url what is the URL of the exchange that made the transfer
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for @a cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_PostTransfersHandle *
+TALER_MERCHANT_transfers_post (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const struct TALER_Amount *credit_amount,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const char *payto_uri,
+  const char *exchange_url,
+  TALER_MERCHANT_PostTransfersCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MERCHANT_PostTransfersHandle *pth;
+  CURL *eh;
+  json_t *req;
+
+  pth = GNUNET_new (struct TALER_MERCHANT_PostTransfersHandle);
+  pth->ctx = ctx;
+  pth->cb = cb;
+  pth->cb_cls = cb_cls;
+  pth->url = TALER_url_join (backend_url,
+                             "private/transfers",
+                             NULL);
+  if (NULL == pth->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (pth);
+    return NULL;
+  }
+  req = json_pack ("{s:o, s:o, s:s, s:s}",
+                   "credit_amount", TALER_JSON_from_amount (credit_amount),
+                   "wtid", GNUNET_JSON_from_data_auto (wtid),
+                   "payto_uri", payto_uri,
+                   "exchange_url", exchange_url);
+  GNUNET_assert (NULL != req);
+  eh = curl_easy_init ();
+  GNUNET_assert (NULL != eh);
+  if (GNUNET_OK != TALER_curl_easy_post (&pth->post_ctx,
+                                         eh,
+                                         req))
+  {
+    GNUNET_break (0);
+    json_decref (req);
+    GNUNET_free (pth);
+    return NULL;
+  }
+  json_decref (req);
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   pth->url));
+  pth->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   pth->post_ctx.headers,
+                                   &handle_post_transfers_finished,
+                                   pth);
+  return pth;
+}
+
+
+/**
+ * Cancel a POST /transfers request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param pth handle to the tracking operation being cancelled
+ */
+void
+TALER_MERCHANT_transfers_post_cancel (
+  struct TALER_MERCHANT_PostTransfersHandle *pth)
+{
+  if (NULL != pth->job)
+  {
+    GNUNET_CURL_job_cancel (pth->job);
+    pth->job = NULL;
+  }
+  GNUNET_free (pth->url);
+  TALER_curl_easy_post_finished (&pth->post_ctx);
+  GNUNET_free (pth);
+}
+
+
+/* end of merchant_api_post_transfers.c */
diff --git a/src/lib/merchant_api_refund.c b/src/lib/merchant_api_refund.c
deleted file mode 100644
index c056c78..0000000
--- a/src/lib/merchant_api_refund.c
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2020 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_refund.c
- * @brief Implementation of the /refund POST and GET
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#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 <taler/taler_curl_lib.h>
-
-
-/**
- * Handle to the refund lookup operation.
- */
-struct TALER_MERCHANT_RefundLookupOperation
-{
-  /**
-   * URL of the request, includes parameters
-   */
-  char *url;
-
-  /**
-   * Handle of the request
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the response
-   */
-  TALER_MERCHANT_RefundLookupCallback cb;
-
-  /**
-   * Closure for cb
-   */
-  void *cb_cls;
-
-  /**
-   * Reference to the execution context
-   */
-  struct GNUNET_CURL_Context *ctx;
-
-};
-
-
-/**
- * Cancel a /refund lookup operation
- *
- * @param rlo operation to cancel
- */
-void
-TALER_MERCHANT_refund_lookup_cancel (
-  struct TALER_MERCHANT_RefundLookupOperation *rlo)
-{
-  if (NULL != rlo->job)
-  {
-    GNUNET_CURL_job_cancel (rlo->job);
-    rlo->job = NULL;
-  }
-  GNUNET_free (rlo->url);
-  GNUNET_free (rlo);
-}
-
-
-/**
- * Check that the @a reply to the @a rlo is valid
- *
- * @param rlo lookup operation
- * @param reply JSON reply to verify
- * @return #TALER_EC_NONE if @a reply is well-formed
- */
-static enum TALER_ErrorCode
-check_refund_result (struct TALER_MERCHANT_RefundLookupOperation *rlo,
-                     const json_t *reply)
-{
-  json_t *refunds;
-  unsigned int num_refunds;
-  struct GNUNET_HashCode h_contract_terms;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_json ("refunds", &refunds),
-    GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &h_contract_terms),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (reply,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
-  }
-  num_refunds = json_array_size (refunds);
-  {
-    struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (num_refunds)];
-    json_t *ercp[GNUNET_NZL (num_refunds)];
-
-    memset (rds,
-            0,
-            sizeof (rds));
-    memset (ercp,
-            0,
-            sizeof (ercp));
-    for (unsigned int i = 0; i<num_refunds; i++)
-    {
-      struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
-      json_t *refund = json_array_get (refunds, i);
-      uint32_t hs;
-      struct GNUNET_JSON_Specification spec_detail[] = {
-        GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                     &rd->coin_pub),
-        TALER_JSON_spec_amount ("refund_amount",
-                                &rd->refund_amount),
-        TALER_JSON_spec_amount ("refund_fee",
-                                &rd->refund_fee),
-        GNUNET_JSON_spec_uint32 ("exchange_http_status",
-                                 &hs),
-        GNUNET_JSON_spec_uint64 ("rtransaction_id",
-                                 &rd->rtransaction_id),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (refund,
-                             spec_detail,
-                             NULL, NULL))
-      {
-        GNUNET_break_op (0);
-        GNUNET_JSON_parse_free (spec);
-        return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
-      }
-      rd->hr.http_status = (unsigned int) hs;
-    }
-
-    for (unsigned int i = 0; i<num_refunds; i++)
-    {
-      struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
-      json_t *refund = json_array_get (refunds, i);
-
-      if (MHD_HTTP_OK == rd->hr.http_status)
-      {
-        struct GNUNET_JSON_Specification spec_detail[] = {
-          GNUNET_JSON_spec_fixed_auto ("exchange_pub",
-                                       &rd->exchange_pub),
-          GNUNET_JSON_spec_fixed_auto ("exchange_sig",
-                                       &rd->exchange_sig),
-          GNUNET_JSON_spec_end ()
-        };
-
-        if (GNUNET_OK !=
-            GNUNET_JSON_parse (refund,
-                               spec_detail,
-                               NULL, NULL))
-        {
-          GNUNET_break_op (0);
-          for (unsigned int j = 0; j<i; j++)
-            if (NULL != ercp[j])
-              json_decref (ercp[j]);
-          GNUNET_JSON_parse_free (spec);
-          return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
-        }
-        /* verify exchange sig (we should not trust the merchant) */
-        {
-          struct TALER_RefundConfirmationPS depconf = {
-            .purpose.size = htonl (sizeof (depconf)),
-            .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
-            .h_contract_terms = h_contract_terms,
-            .coin_pub = rd->coin_pub,
-            .merchant = merchant_pub,
-            .rtransaction_id = GNUNET_htonll (rd->rtransaction_id)
-          };
-
-          TALER_amount_hton (&depconf.refund_amount,
-                             &rd->refund_amount);
-          TALER_amount_hton (&depconf.refund_fee,
-                             &rd->refund_fee);
-          if (GNUNET_OK !=
-              GNUNET_CRYPTO_eddsa_verify (
-                TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
-                &depconf,
-                &rd->exchange_sig.eddsa_signature,
-                &rd->exchange_pub.eddsa_pub))
-          {
-            /* While the *exchange* signature is invalid, we do blame the
-               merchant here, because the merchant should have checked and
-               sent us an error code (with exchange HTTP status code 0) instead
-               of claiming that the exchange yielded a good response. *///
-            GNUNET_break_op (0);
-            GNUNET_JSON_parse_free (spec);
-            return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
-          }
-        }
-      }
-      else
-      {
-        uint32_t ec;
-        struct GNUNET_JSON_Specification spec_detail[] = {
-          GNUNET_JSON_spec_uint32 ("exchange_code",
-                                   &ec),
-          GNUNET_JSON_spec_end ()
-        };
-
-        if (GNUNET_OK !=
-            GNUNET_JSON_parse (refund,
-                               spec_detail,
-                               NULL, NULL))
-        {
-          GNUNET_break_op (0);
-          rd->hr.ec = TALER_EC_INVALID;
-        }
-        ercp[i] = json_incref (json_object_get (refund,
-                                                "exchange_reply"));
-        rd->hr.reply = ercp[i];
-      }
-    }
-    {
-      struct TALER_MERCHANT_HttpResponse hr = {
-        .http_status = MHD_HTTP_OK,
-        .reply = reply
-      };
-      rlo->cb (rlo->cb_cls,
-               &hr,
-               &h_contract_terms,
-               &merchant_pub,
-               num_refunds,
-               rds);
-    }
-    for (unsigned int j = 0; j<num_refunds; j++)
-      if (NULL != ercp[j])
-        json_decref (ercp[j]);
-  }
-  GNUNET_JSON_parse_free (spec);
-  return TALER_EC_NONE;
-}
-
-
-/**
- * Process GET /refund response
- *
- * @param cls a `struct TALER_MERCHANT_RefundLookupOperation *`
- * @param response_code HTTP status, 0 for HTTP failure
- * @param response a `const json_t *` with the JSON of the HTTP body
- */
-static void
-handle_refund_lookup_finished (void *cls,
-                               long response_code,
-                               const void *response)
-{
-  struct TALER_MERCHANT_RefundLookupOperation *rlo = cls;
-  const json_t *json = response;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
-  };
-
-  rlo->job = NULL;
-  switch (response_code)
-  {
-  case 0:
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Backend didn't even return from GET /refund\n");
-    hr.ec = TALER_EC_INVALID_RESPONSE;
-    break;
-  case MHD_HTTP_OK:
-    if (TALER_EC_NONE ==
-        (hr.ec = check_refund_result (rlo,
-                                      json)))
-    {
-      TALER_MERCHANT_refund_lookup_cancel (rlo);
-      return;
-    }
-    /* failure, report! */
-    hr.http_status = 0;
-    break;
-  case MHD_HTTP_NOT_FOUND:
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    break;
-  default:
-    GNUNET_break_op (0); /* unexpected status code */
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    break;
-  }
-  rlo->cb (rlo->cb_cls,
-           &hr,
-           NULL,
-           NULL,
-           0,
-           NULL);
-  TALER_MERCHANT_refund_lookup_cancel (rlo);
-}
-
-
-/**
- * Does a GET /refund.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param order_id order id used to perform the lookup
- * @param cb callback which will work the response gotten from the backend
- * @param cb_cls closure to pass to the callback
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_RefundLookupOperation *
-TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
-                              const char *backend_url,
-                              const char *order_id,
-                              TALER_MERCHANT_RefundLookupCallback cb,
-                              void *cb_cls)
-{
-  struct TALER_MERCHANT_RefundLookupOperation *rlo;
-  CURL *eh;
-
-  rlo = GNUNET_new (struct TALER_MERCHANT_RefundLookupOperation);
-  rlo->ctx = ctx;
-  rlo->cb = cb;
-  rlo->cb_cls = cb_cls;
-  rlo->url = TALER_url_join (backend_url,
-                             "refund",
-                             "order_id",
-                             order_id,
-                             NULL);
-  eh = curl_easy_init ();
-  if (CURLE_OK != curl_easy_setopt (eh,
-                                    CURLOPT_URL,
-                                    rlo->url))
-  {
-    GNUNET_break (0);
-    GNUNET_free (rlo->url);
-    GNUNET_free (rlo);
-    return NULL;
-  }
-  rlo->job = GNUNET_CURL_job_add (ctx,
-                                  eh,
-                                  GNUNET_NO,
-                                  handle_refund_lookup_finished,
-                                  rlo);
-  if (NULL == rlo->job)
-  {
-    GNUNET_free (rlo->url);
-    GNUNET_free (rlo);
-    GNUNET_break (0);
-    return NULL;
-  }
-  return rlo;
-}
diff --git a/src/lib/merchant_api_tip_authorize.c 
b/src/lib/merchant_api_tip_authorize.c
index 37ad390..01d369f 100644
--- a/src/lib/merchant_api_tip_authorize.c
+++ b/src/lib/merchant_api_tip_authorize.c
@@ -35,7 +35,7 @@
 /**
  * @brief A handle for tip authorizations.
  */
-struct TALER_MERCHANT_TipAuthorizeOperation
+struct TALER_MERCHANT_TipAuthorizeHandle
 {
 
   /**
@@ -80,13 +80,15 @@ struct TALER_MERCHANT_TipAuthorizeOperation
  * @return #GNUNET_OK if response is valid
  */
 static int
-check_ok (struct TALER_MERCHANT_TipAuthorizeOperation *tao,
+check_ok (struct TALER_MERCHANT_TipAuthorizeHandle *tao,
           const json_t *json)
 {
-  const char *taler_tip_uri;
+  const char *taler_tip_url;
   struct GNUNET_HashCode tip_id;
+  struct GNUNET_TIME_Absolute expiration_time;
   struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_string ("taler_tip_uri", &taler_tip_uri),
+    GNUNET_JSON_spec_string ("tip_redirect_url", &taler_tip_url),
+    GNUNET_JSON_spec_absolute_time ("tip_expiration", &expiration_time),
     GNUNET_JSON_spec_fixed_auto ("tip_id", &tip_id),
     GNUNET_JSON_spec_end ()
   };
@@ -114,7 +116,8 @@ check_ok (struct TALER_MERCHANT_TipAuthorizeOperation *tao,
   tao->cb (tao->cb_cls,
            &hr,
            &tip_id,
-           taler_tip_uri);
+           taler_tip_url,
+           expiration_time);
   tao->cb = NULL; /* do not call twice */
   GNUNET_JSON_parse_free (spec);
   return GNUNET_OK;
@@ -123,9 +126,9 @@ check_ok (struct TALER_MERCHANT_TipAuthorizeOperation *tao,
 
 /**
  * Function called when we're done processing the
- * HTTP /track/transaction request.
+ * HTTP /reservers/$TIP_ID/tip-authorize request.
  *
- * @param cls the `struct TALER_MERCHANT_TipAuthorizeOperation`
+ * @param cls the `struct TALER_MERCHANT_TipAuthorizeHandle`
  * @param response_code HTTP response code, 0 on error
  * @param json response body, NULL if not in JSON
  */
@@ -134,7 +137,7 @@ handle_tip_authorize_finished (void *cls,
                                long response_code,
                                const void *response)
 {
-  struct TALER_MERCHANT_TipAuthorizeOperation *tao = cls;
+  struct TALER_MERCHANT_TipAuthorizeHandle *tao = cls;
   const json_t *json = response;
   struct TALER_MERCHANT_HttpResponse hr = {
     .http_status = (unsigned int) response_code,
@@ -195,7 +198,8 @@ handle_tip_authorize_finished (void *cls,
     tao->cb (tao->cb_cls,
              &hr,
              NULL,
-             NULL);
+             NULL,
+             GNUNET_TIME_UNIT_ZERO_ABS);
   TALER_MERCHANT_tip_authorize_cancel (tao);
 }
 
@@ -206,7 +210,111 @@ handle_tip_authorize_finished (void *cls,
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
- * @param pickup_url frontend URL for where the tip can be picked up
+ * @param reserve_pub public key of the reserve
+ * @param next_url where the browser should proceed after picking up the tip
+ * @param amount amount to be handed out as a tip
+ * @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_TipAuthorizeHandle *
+TALER_MERCHANT_tip_authorize2 (
+  struct GNUNET_CURL_Context *ctx,
+  const char *backend_url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *next_url,
+  const struct TALER_Amount *amount,
+  const char *justification,
+  TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
+  void *authorize_cb_cls)
+{
+  struct TALER_MERCHANT_TipAuthorizeHandle *tao;
+  CURL *eh;
+  json_t *te_obj;
+
+  tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeHandle);
+  tao->ctx = ctx;
+  tao->cb = authorize_cb;
+  tao->cb_cls = authorize_cb_cls;
+
+  {
+    char res_str[sizeof (*reserve_pub) * 2];
+    char arg_str[sizeof (res_str) + 48];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (reserve_pub,
+                                         sizeof (*reserve_pub),
+                                         res_str,
+                                         sizeof (res_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "private/reserves/%s/authorize-tip",
+                     res_str);
+    tao->url = TALER_url_join (backend_url,
+                               arg_str,
+                               NULL);
+  }
+  if (NULL == tao->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (tao);
+    return NULL;
+  }
+  te_obj = json_pack ("{"
+                      " s:o," /* amount */
+                      " s:s," /* justification */
+                      " s:s," /* next_url */
+                      "}",
+                      "amount", TALER_JSON_from_amount (amount),
+                      "justification", justification,
+                      "next_url", next_url);
+  if (NULL == te_obj)
+  {
+    GNUNET_break (0);
+    GNUNET_free (tao->url);
+    GNUNET_free (tao);
+    return NULL;
+  }
+
+  eh = curl_easy_init ();
+  if (GNUNET_OK != TALER_curl_easy_post (&tao->post_ctx,
+                                         eh,
+                                         te_obj))
+  {
+    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 URL '%s'\n",
+              tao->url);
+  GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
+                                               CURLOPT_URL,
+                                               tao->url));
+
+  tao->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   tao->post_ctx.headers,
+                                   &handle_tip_authorize_finished,
+                                   tao);
+  return tao;
+}
+
+
+/**
+ * Issue a POST /tips request to the backend.  Informs the backend that a tip
+ * should be created. In contrast to #TALER_MERCHANT_tip_authorize2(), the
+ * backend gets to pick the reserve with this API.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
  * @param next_url where the browser should proceed after picking up the tip
  * @param amount amount to be handed out as a tip
  * @param justification which justification should be stored (human-readable 
reason for the tip)
@@ -214,26 +322,26 @@ handle_tip_authorize_finished (void *cls,
  * @param authorize_cb_cls closure to pass to @a authorize_cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_TipAuthorizeOperation *
+struct TALER_MERCHANT_TipAuthorizeHandle *
 TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context *ctx,
                               const char *backend_url,
-                              const char *pickup_url,
                               const char *next_url,
                               const struct TALER_Amount *amount,
                               const char *justification,
                               TALER_MERCHANT_TipAuthorizeCallback authorize_cb,
                               void *authorize_cb_cls)
 {
-  struct TALER_MERCHANT_TipAuthorizeOperation *tao;
+  struct TALER_MERCHANT_TipAuthorizeHandle *tao;
   CURL *eh;
   json_t *te_obj;
 
-  tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeOperation);
+  tao = GNUNET_new (struct TALER_MERCHANT_TipAuthorizeHandle);
   tao->ctx = ctx;
   tao->cb = authorize_cb;
   tao->cb_cls = authorize_cb_cls;
+
   tao->url = TALER_url_join (backend_url,
-                             "tip-authorize",
+                             "private/tips",
                              NULL);
   if (NULL == tao->url)
   {
@@ -245,12 +353,10 @@ TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context 
*ctx,
   te_obj = json_pack ("{"
                       " s:o," /* amount */
                       " s:s," /* justification */
-                      " s:s," /* pickup_url */
                       " s:s," /* next_url */
                       "}",
                       "amount", TALER_JSON_from_amount (amount),
                       "justification", justification,
-                      "pickup_url", pickup_url,
                       "next_url", next_url);
   if (NULL == te_obj)
   {
@@ -297,7 +403,7 @@ TALER_MERCHANT_tip_authorize (struct GNUNET_CURL_Context 
*ctx,
  */
 void
 TALER_MERCHANT_tip_authorize_cancel (
-  struct TALER_MERCHANT_TipAuthorizeOperation *tao)
+  struct TALER_MERCHANT_TipAuthorizeHandle *tao)
 {
   if (NULL != tao->job)
   {
diff --git a/src/lib/merchant_api_tip_pickup.c 
b/src/lib/merchant_api_tip_pickup.c
index 6e48f16..08f5935 100644
--- a/src/lib/merchant_api_tip_pickup.c
+++ b/src/lib/merchant_api_tip_pickup.c
@@ -57,7 +57,7 @@ struct PlanchetData
 /**
  * Handle for a /tip-pickup operation.
  */
-struct TALER_MERCHANT_TipPickupOperation
+struct TALER_MERCHANT_TipPickupHandle
 {
 
   /**
@@ -73,7 +73,7 @@ struct TALER_MERCHANT_TipPickupOperation
   /**
    * Handle for the actual (internal) withdraw operation.
    */
-  struct TALER_MERCHANT_TipPickup2Operation *tpo2;
+  struct TALER_MERCHANT_TipPickup2Handle *tpo2;
 
   /**
    * Number of planchets/coins used for this operation.
@@ -92,7 +92,7 @@ struct TALER_MERCHANT_TipPickupOperation
  * Callback for a /tip-pickup request.  Returns the result of the operation.
  * Note that the client MUST still do the unblinding of the @a blind_sigs.
  *
- * @param cls closure, a `struct TALER_MERCHANT_TipPickupOperation *`
+ * @param cls closure, a `struct TALER_MERCHANT_TipPickupHandle *`
  * @param hr HTTP response details
  * @param num_blind_sigs length of the @a reserve_sigs array, 0 on error
  * @param blind_sigs array of blind signatures over the planchets, NULL on 
error
@@ -103,7 +103,7 @@ pickup_done_cb (void *cls,
                 unsigned int num_blind_sigs,
                 const struct TALER_MERCHANT_BlindSignature *blind_sigs)
 {
-  struct TALER_MERCHANT_TipPickupOperation *tp = cls;
+  struct TALER_MERCHANT_TipPickupHandle *tp = cls;
 
   tp->tpo2 = NULL;
   if (NULL == blind_sigs)
@@ -180,7 +180,7 @@ pickup_done_cb (void *cls,
  * @param pickup_cb_cls closure to pass to @a pickup_cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_TipPickupOperation *
+struct TALER_MERCHANT_TipPickupHandle *
 TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
                            const char *backend_url,
                            const struct GNUNET_HashCode *tip_id,
@@ -189,7 +189,7 @@ TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
                            TALER_MERCHANT_TipPickupCallback pickup_cb,
                            void *pickup_cb_cls)
 {
-  struct TALER_MERCHANT_TipPickupOperation *tp;
+  struct TALER_MERCHANT_TipPickupHandle *tp;
   struct TALER_PlanchetDetail details[GNUNET_NZL (num_planchets)];
 
   if (0 == num_planchets)
@@ -197,7 +197,7 @@ TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
     GNUNET_break (0);
     return NULL;
   }
-  tp = GNUNET_new (struct TALER_MERCHANT_TipPickupOperation);
+  tp = GNUNET_new (struct TALER_MERCHANT_TipPickupHandle);
   GNUNET_array_grow (tp->planchets,
                      tp->num_planchets,
                      num_planchets);
@@ -249,7 +249,7 @@ TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
  * @param tp handle from the operation to cancel
  */
 void
-TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupOperation *tp)
+TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupHandle *tp)
 {
   for (unsigned int i = 0; i<tp->num_planchets; i++)
     GNUNET_CRYPTO_rsa_public_key_dup (tp->planchets[i].pk.key.rsa_public_key);
diff --git a/src/lib/merchant_api_tip_pickup2.c 
b/src/lib/merchant_api_tip_pickup2.c
index 751d126..5422a1b 100644
--- a/src/lib/merchant_api_tip_pickup2.c
+++ b/src/lib/merchant_api_tip_pickup2.c
@@ -35,7 +35,7 @@
 /**
  * @brief A handle for tracking transactions.
  */
-struct TALER_MERCHANT_TipPickup2Operation
+struct TALER_MERCHANT_TipPickup2Handle
 {
 
   /**
@@ -85,7 +85,7 @@ struct TALER_MERCHANT_TipPickup2Operation
  * @return #GNUNET_OK if response is valid
  */
 static int
-check_ok (struct TALER_MERCHANT_TipPickup2Operation *tpo,
+check_ok (struct TALER_MERCHANT_TipPickup2Handle *tpo,
           const json_t *json)
 {
   json_t *ja;
@@ -154,7 +154,7 @@ check_ok (struct TALER_MERCHANT_TipPickup2Operation *tpo,
  * Function called when we're done processing the
  * HTTP /track/transaction request.
  *
- * @param cls the `struct TALER_MERCHANT_TipPickupOperation`
+ * @param cls the `struct TALER_MERCHANT_TipPickupHandle`
  * @param response_code HTTP response code, 0 on error
  * @param json response body, NULL if not in JSON
  */
@@ -163,7 +163,7 @@ handle_tip_pickup_finished (void *cls,
                             long response_code,
                             const void *response)
 {
-  struct TALER_MERCHANT_TipPickup2Operation *tpo = cls;
+  struct TALER_MERCHANT_TipPickup2Handle *tpo = cls;
   const json_t *json = response;
   struct TALER_MERCHANT_HttpResponse hr = {
     .http_status = (unsigned int) response_code,
@@ -223,7 +223,7 @@ handle_tip_pickup_finished (void *cls,
 
 
 /**
- * Issue a /tip-pickup request to the backend.  Informs the backend
+ * Issue a /tips/$ID/tip-pickup request to the backend.  Informs the backend
  * that a customer wants to pick up a tip.
  *
  * @param ctx execution context
@@ -235,16 +235,16 @@ handle_tip_pickup_finished (void *cls,
  * @param pickup_cb_cls closure to pass to @a pickup_cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_TipPickup2Operation *
+struct TALER_MERCHANT_TipPickup2Handle *
 TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context *ctx,
                             const char *backend_url,
                             const struct GNUNET_HashCode *tip_id,
                             unsigned int num_planchets,
-                            struct TALER_PlanchetDetail *planchets,
+                            const struct TALER_PlanchetDetail planchets[],
                             TALER_MERCHANT_TipPickup2Callback pickup_cb,
                             void *pickup_cb_cls)
 {
-  struct TALER_MERCHANT_TipPickup2Operation *tpo;
+  struct TALER_MERCHANT_TipPickup2Handle *tpo;
   CURL *eh;
   json_t *pa;
   json_t *tp_obj;
@@ -283,26 +283,37 @@ TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context 
*ctx,
       return NULL;
     }
   }
-  tp_obj = json_pack ("{"
-                      " s:o," /* tip_id */
-                      " s:o," /* planchets */
-                      "}",
-                      "tip_id", GNUNET_JSON_from_data_auto (tip_id),
+  tp_obj = json_pack ("{s:o}",
                       "planchets", pa);
   if (NULL == tp_obj)
   {
     GNUNET_break (0);
     return NULL;
   }
-  tpo = GNUNET_new (struct TALER_MERCHANT_TipPickup2Operation);
+  tpo = GNUNET_new (struct TALER_MERCHANT_TipPickup2Handle);
   tpo->num_planchets = num_planchets;
   tpo->ctx = ctx;
   tpo->cb = pickup_cb;
   tpo->cb_cls = pickup_cb_cls;
 
-  tpo->url = TALER_url_join (backend_url,
-                             "tip-pickup",
-                             NULL);
+  {
+    char tip_str[sizeof (*tip_id) * 2];
+    char arg_str[sizeof (tip_str) + 32];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (tip_id,
+                                         sizeof (*tip_id),
+                                         tip_str,
+                                         sizeof (tip_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "tips/%s/pickup",
+                     tip_str);
+    tpo->url = TALER_url_join (backend_url,
+                               arg_str,
+                               NULL);
+  }
   if (NULL == tpo->url)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -340,14 +351,14 @@ TALER_MERCHANT_tip_pickup2 (struct GNUNET_CURL_Context 
*ctx,
 
 
 /**
- * Cancel a /track/transaction request.  This function cannot be used
+ * Cancel a /tips/$TIP_ID/pickup 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_pickup2_cancel (
-  struct TALER_MERCHANT_TipPickup2Operation *tpo)
+  struct TALER_MERCHANT_TipPickup2Handle *tpo)
 {
   if (NULL != tpo->job)
   {
diff --git a/src/lib/merchant_api_track_transaction.c 
b/src/lib/merchant_api_track_transaction.c
deleted file mode 100644
index 7a091d1..0000000
--- a/src/lib/merchant_api_track_transaction.c
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014, 2015, 2016, 2020 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_track_transaction.c
- * @brief Implementation of the /track/transaction 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>
-
-
-/**
- * @brief A handle for tracking transactions.
- */
-struct TALER_MERCHANT_TrackTransactionHandle
-{
-
-  /**
-   * The url for this request.
-   */
-  char *url;
-
-  /**
-   * Handle for the request.
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the result.
-   */
-  TALER_MERCHANT_TrackTransactionCallback 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_TrackTransactionHandle`
- * @param response_code HTTP response code, 0 on error
- * @param json response body, NULL if not in JSON
- */
-static void
-handle_track_transaction_finished (void *cls,
-                                   long response_code,
-                                   const void *response)
-{
-  struct TALER_MERCHANT_TrackTransactionHandle *tdo = cls;
-  const json_t *json = response;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
-  };
-
-  tdo->job = NULL;
-  switch (response_code)
-  {
-  case 0:
-    hr.ec = TALER_EC_INVALID_RESPONSE;
-    break;
-  case MHD_HTTP_OK:
-    /* FIXME: should we not have a timestamp here as well? */
-    tdo->cb (tdo->cb_cls,
-             &hr);
-    TALER_MERCHANT_track_transaction_cancel (tdo);
-    return;
-  case MHD_HTTP_ACCEPTED:
-    {
-      /* FIXME: Expect time stamp of when the transfer is supposed to happen
-         => Parse it? */
-    }
-    break;
-  case MHD_HTTP_FAILED_DEPENDENCY:
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Exchange gave inconsistent response\n");
-    TALER_MERCHANT_parse_error_details_ (json,
-                                         response_code,
-                                         &hr);
-    break;
-  case MHD_HTTP_NOT_FOUND:
-    /* Nothing really to verify, this should never
-       happen, we should pass the JSON reply to the application */
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Did not find any data\n");
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    break;
-  case MHD_HTTP_INTERNAL_SERVER_ERROR:
-    /* Server had an internal issue; we should retry, but this API
-       leaves this to the application */
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    break;
-  default:
-    /* unexpected response code */
-    GNUNET_break_op (0);
-    TALER_MERCHANT_parse_error_details_ (json,
-                                         response_code,
-                                         &hr);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d\n",
-                (unsigned int) response_code,
-                (int) hr.ec);
-    break;
-  }
-  tdo->cb (tdo->cb_cls,
-           &hr);
-  TALER_MERCHANT_track_transaction_cancel (tdo);
-}
-
-
-/**
- * Request backend to return transactions associated with a given wtid.
- *
- * @param ctx execution context
- * @param backend_url base URL of the backend
- * @param order_id order id pointing to the transaction being tracked
- * @param track_transaction_cb the callback to call when a reply for this 
request is available
- * @param track_transaction_cb_cls closure for @a track_transaction_cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_TrackTransactionHandle *
-TALER_MERCHANT_track_transaction (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  const char *order_id,
-  TALER_MERCHANT_TrackTransactionCallback track_transaction_cb,
-  void *track_transaction_cb_cls)
-{
-  struct TALER_MERCHANT_TrackTransactionHandle *tdo;
-  CURL *eh;
-
-  tdo = GNUNET_new (struct TALER_MERCHANT_TrackTransactionHandle);
-  tdo->ctx = ctx;
-  tdo->cb = track_transaction_cb;
-  tdo->cb_cls = track_transaction_cb_cls;
-  tdo->url = TALER_url_join (backend_url,
-                             "track/transaction",
-                             "order_id", order_id,
-                             NULL);
-  if (NULL == tdo->url)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not construct request URL.\n");
-    GNUNET_free (tdo);
-    return NULL;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Requesting URL '%s'\n",
-              tdo->url);
-  eh = curl_easy_init ();
-  GNUNET_assert (CURLE_OK ==
-                 curl_easy_setopt (eh,
-                                   CURLOPT_URL,
-                                   tdo->url));
-  tdo->job = GNUNET_CURL_job_add (ctx,
-                                  eh,
-                                  GNUNET_YES,
-                                  &handle_track_transaction_finished,
-                                  tdo);
-  return tdo;
-}
-
-
-/**
- * Cancel a /track/transaction request.  This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param tdo handle to the tracking operation being cancelled
- */
-void
-TALER_MERCHANT_track_transaction_cancel (
-  struct TALER_MERCHANT_TrackTransactionHandle *tdo)
-{
-  if (NULL != tdo->job)
-  {
-    GNUNET_CURL_job_cancel (tdo->job);
-    tdo->job = NULL;
-  }
-  GNUNET_free (tdo->url);
-  GNUNET_free (tdo);
-}
-
-
-/* end of merchant_api_track_transaction.c */
diff --git a/src/lib/merchant_api_track_transfer.c 
b/src/lib/merchant_api_track_transfer.c
deleted file mode 100644
index 5743f2f..0000000
--- a/src/lib/merchant_api_track_transfer.c
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2017, 2020 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_track_transfer.c
- * @brief Implementation of the /track/transfer 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>
-
-
-/**
- * @brief A Handle for tracking wire transfers.
- */
-struct TALER_MERCHANT_TrackTransferHandle
-{
-
-  /**
-   * The url for this request.
-   */
-  char *url;
-
-  /**
-   * Handle for the request.
-   */
-  struct GNUNET_CURL_Job *job;
-
-  /**
-   * Function to call with the result.
-   */
-  TALER_MERCHANT_TrackTransferCallback cb;
-
-  /**
-   * Closure for @a cb.
-   */
-  void *cb_cls;
-
-  /**
-   * Reference to the execution context.
-   */
-  struct GNUNET_CURL_Context *ctx;
-};
-
-
-/**
- * We got a #MHD_HTTP_OK response for the /track/transfer request.
- * Check that the response is well-formed and if it is, call the
- * callback.  If not, return an error code.
- *
- * This code is very similar to
- * exchange_api_transfers_get.c::check_transfers_get_response_ok.
- * (Except we do not check the signature, as that was done by the
- * backend which we trust already.)
- * Any changes should likely be reflected there as well.
- *
- * @param wdh handle to the operation
- * @param json response we got
- * @return #GNUNET_OK if we are done and all is well,
- *         #GNUNET_SYSERR if the response was bogus
- */
-static int
-check_transfers_get_response_ok (
-  struct TALER_MERCHANT_TrackTransferHandle *wdh,
-  const json_t *json)
-{
-  json_t *deposits;
-  struct GNUNET_HashCode h_wire;
-  struct TALER_Amount total_amount;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  unsigned int num_details;
-  struct TALER_ExchangePublicKeyP exchange_pub;
-  struct GNUNET_JSON_Specification inner_spec[] = {
-    TALER_JSON_spec_amount ("total", &total_amount),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
-    GNUNET_JSON_spec_fixed_auto ("h_wire", &h_wire),
-    GNUNET_JSON_spec_json ("deposits_sums", &deposits),
-    GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub),
-    GNUNET_JSON_spec_end ()
-  };
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = MHD_HTTP_OK,
-    .reply = json
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (json,
-                         inner_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  num_details = json_array_size (deposits);
-  {
-    struct TALER_MERCHANT_TrackTransferDetails details[num_details];
-
-    for (unsigned int i = 0; i<num_details; i++)
-    {
-      struct TALER_MERCHANT_TrackTransferDetails *detail = &details[i];
-      json_t *deposit = json_array_get (deposits, i);
-      struct GNUNET_JSON_Specification spec_detail[] = {
-        GNUNET_JSON_spec_string ("order_id", &detail->order_id),
-        TALER_JSON_spec_amount ("deposit_value", &detail->deposit_value),
-        TALER_JSON_spec_amount ("deposit_fee", &detail->deposit_fee),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (deposit,
-                             spec_detail,
-                             NULL, NULL))
-      {
-        GNUNET_break_op (0);
-        GNUNET_JSON_parse_free (inner_spec);
-        return GNUNET_SYSERR;
-      }
-    }
-    wdh->cb (wdh->cb_cls,
-             &hr,
-             &exchange_pub,
-             &h_wire,
-             &total_amount,
-             num_details,
-             details);
-  }
-  GNUNET_JSON_parse_free (inner_spec);
-  return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /track/transfer request.
- *
- * @param cls the `struct TALER_MERCHANT_TrackTransferHandle`
- * @param response_code HTTP response code, 0 on error
- * @param json response body, NULL if not in JSON
- */
-static void
-handle_transfers_get_finished (void *cls,
-                               long response_code,
-                               const void *response)
-{
-  struct TALER_MERCHANT_TrackTransferHandle *tdo = cls;
-  const json_t *json = response;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
-  };
-
-  tdo->job = NULL;
-  switch (response_code)
-  {
-  case 0:
-    hr.ec = TALER_EC_INVALID_RESPONSE;
-    break;
-  case MHD_HTTP_OK:
-    if (GNUNET_OK ==
-        check_transfers_get_response_ok (tdo,
-                                         json))
-    {
-      TALER_MERCHANT_track_transfer_cancel (tdo);
-      return;
-    }
-    GNUNET_break_op (0);
-    hr.http_status = 0;
-    hr.ec = TALER_EC_INVALID_RESPONSE; // TODO: use more specific code!
-    break;
-  case MHD_HTTP_FAILED_DEPENDENCY:
-    /* Not a reason to break execution.  */
-    TALER_MERCHANT_parse_error_details_ (json,
-                                         response_code,
-                                         &hr);
-    break;
-  case MHD_HTTP_NOT_FOUND:
-    /* Nothing really to verify, this should never
-       happen, we should pass the JSON reply to the application */
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    break;
-  case MHD_HTTP_INTERNAL_SERVER_ERROR:
-    /* Server had an internal issue; we should retry, but this API
-       leaves this to the application */
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
-    break;
-  default:
-    /* unexpected response code */
-    GNUNET_break_op (0);
-    TALER_MERCHANT_parse_error_details_ (json,
-                                         response_code,
-                                         &hr);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u/%d\n",
-                (unsigned int) response_code,
-                (int) hr.ec);
-    response_code = 0;
-    break;
-  }
-  tdo->cb (tdo->cb_cls,
-           &hr,
-           NULL,
-           NULL,
-           NULL,
-           0,
-           NULL);
-  TALER_MERCHANT_track_transfer_cancel (tdo);
-}
-
-
-/**
- * Request backend to return transfers associated with a given wtid.
- *
- * @param ctx execution context
- * @param backend_url base URL of the backend
- * @param wire_method wire method used for the wire transfer
- * @param wtid base32 string indicating a wtid
- * @param exchange_url base URL of the exchange in charge of returning the 
wanted information
- * @param track_transfer_cb the callback to call when a reply for this request 
is available
- * @param track_transfer_cb_cls closure for @a contract_cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_TrackTransferHandle *
-TALER_MERCHANT_track_transfer (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  const char *wire_method,
-  const struct TALER_WireTransferIdentifierRawP *wtid,
-  const char *exchange_url,
-  TALER_MERCHANT_TrackTransferCallback track_transfer_cb,
-  void *track_transfer_cb_cls)
-{
-  struct TALER_MERCHANT_TrackTransferHandle *tdo;
-  CURL *eh;
-  char *wtid_str;
-
-  wtid_str = GNUNET_STRINGS_data_to_string_alloc (
-    wtid,
-    sizeof (struct TALER_WireTransferIdentifierRawP));
-  tdo = GNUNET_new (struct TALER_MERCHANT_TrackTransferHandle);
-  tdo->ctx = ctx;
-  tdo->cb = track_transfer_cb; // very last to be called
-  tdo->cb_cls = track_transfer_cb_cls;
-  tdo->url = TALER_url_join (backend_url, "track/transfer",
-                             "wtid", wtid_str,
-                             "exchange", exchange_url,
-                             "wire_method", wire_method,
-                             NULL);
-  GNUNET_free (wtid_str);
-  if (NULL == tdo->url)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Could not construct request URL.\n");
-    GNUNET_free (tdo);
-    return NULL;
-  }
-  eh = curl_easy_init ();
-  GNUNET_assert (CURLE_OK ==
-                 curl_easy_setopt (eh,
-                                   CURLOPT_URL,
-                                   tdo->url));
-  tdo->job = GNUNET_CURL_job_add (ctx,
-                                  eh,
-                                  GNUNET_YES,
-                                  &handle_transfers_get_finished,
-                                  tdo);
-  return tdo;
-}
-
-
-/**
- * Cancel a /track/transfer request.  This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param tdo handle to the tracking operation being cancelled
- */
-void
-TALER_MERCHANT_track_transfer_cancel (
-  struct TALER_MERCHANT_TrackTransferHandle *tdo)
-{
-  if (NULL != tdo->job)
-  {
-    GNUNET_CURL_job_cancel (tdo->job);
-    tdo->job = NULL;
-  }
-  GNUNET_free (tdo->url);
-  GNUNET_free (tdo);
-}
-
-
-/* end of merchant_api_track_transfer.c */
diff --git a/src/lib/merchant_api_wallet_get_order.c 
b/src/lib/merchant_api_wallet_get_order.c
new file mode 100644
index 0000000..799e57f
--- /dev/null
+++ b/src/lib/merchant_api_wallet_get_order.c
@@ -0,0 +1,510 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018, 2020 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_wallet_get_order.c
+ * @brief Implementation of the GET /orders/$ID request
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ * @author Florian Dold
+ */
+#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>
+
+
+/**
+ * @brief A GET /orders/$ID handle
+ */
+struct TALER_MERCHANT_OrderWalletGetHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_MERCHANT_OrderWalletGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Convenience function to call the callback in @a owgh with an error code of
+ * @a ec and the exchange body being set to @a reply.
+ *
+ * @param owgh handle providing callback
+ * @param ec error code to return to application
+ * @param reply JSON reply we got from the exchange, can be NULL
+ */
+static void
+cb_failure (struct TALER_MERCHANT_OrderWalletGetHandle *owgh,
+            enum TALER_ErrorCode ec,
+            const json_t *reply)
+{
+  struct TALER_MERCHANT_HttpResponse hr = {
+    .ec = TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
+    .reply = reply
+  };
+
+  owgh->cb (owgh->cb_cls,
+            &hr,
+            GNUNET_SYSERR,
+            GNUNET_SYSERR,
+            NULL,
+            NULL,
+            NULL,
+            NULL,
+            0,
+            NULL);
+}
+
+
+/**
+ * Function called when we're done processing the GET /check-payment request.
+ *
+ * @param cls the `struct TALER_MERCHANT_OrderWalletGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param json response body, should be NULL
+ */
+static void
+handle_wallet_get_order_finished (void *cls,
+                                  long response_code,
+                                  const void *response)
+{
+  struct TALER_MERCHANT_OrderWalletGetHandle *owgh = cls;
+  const json_t *json = response;
+
+  owgh->job = NULL;
+  if (MHD_HTTP_OK != response_code)
+  {
+    struct TALER_MERCHANT_HttpResponse hr;
+
+    TALER_MERCHANT_parse_error_details_ (response,
+                                         response_code,
+                                         &hr);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Checking order status failed with HTTP status code %u/%d\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    GNUNET_break_op (0);
+    owgh->cb (owgh->cb_cls,
+              &hr,
+              GNUNET_SYSERR,
+              GNUNET_SYSERR,
+              NULL,
+              NULL,
+              NULL,
+              NULL,
+              0,
+              NULL);
+    TALER_MERCHANT_wallet_order_get_cancel (owgh);
+    return;
+  }
+
+  if (! json_boolean_value (json_object_get (json, "paid")))
+  {
+    /* Status is: unpaid */
+    const char *taler_pay_uri = json_string_value (json_object_get (json,
+                                                                    
"taler_pay_uri"));
+    const char *already_paid = json_string_value (json_object_get (json,
+                                                                   
"already_paid_order_id"));
+    if (NULL == taler_pay_uri)
+    {
+      GNUNET_break_op (0);
+      cb_failure (owgh,
+                  TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
+                  json);
+    }
+    else
+    {
+      struct TALER_MERCHANT_HttpResponse hr = {
+        .reply = json,
+        .http_status = MHD_HTTP_OK
+      };
+
+      owgh->cb (owgh->cb_cls,
+                &hr,
+                GNUNET_NO,
+                GNUNET_NO,
+                NULL,
+                taler_pay_uri,
+                already_paid,
+                NULL,
+                0,
+                NULL);
+    }
+    TALER_MERCHANT_wallet_order_get_cancel (owgh);
+    return;
+  }
+
+  {
+    struct TALER_Amount refund_amount;
+    json_t *refunds;
+    bool refunded;
+    struct TALER_MerchantPublicKeyP merchant_pub;
+    unsigned int refund_len;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_bool ("refunded",
+                             &refunded),
+      GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                   &merchant_pub),
+      TALER_JSON_spec_amount ("refund_amount",
+                              &refund_amount),
+      GNUNET_JSON_spec_json ("refunds",
+                             &refunds),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (json,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      cb_failure (owgh,
+                  TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
+                  json);
+      TALER_MERCHANT_wallet_order_get_cancel (owgh);
+      return;
+    }
+
+    if (! json_is_array (refunds))
+    {
+      GNUNET_break_op (0);
+      cb_failure (owgh,
+                  TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
+                  json);
+      GNUNET_JSON_parse_free (spec);
+      TALER_MERCHANT_wallet_order_get_cancel (owgh);
+      return;
+    }
+
+    refund_len = json_array_size (refunds);
+    {
+      struct TALER_MERCHANT_RefundDetail rds[refund_len];
+
+      memset (rds,
+              0,
+              sizeof (rds));
+      for (unsigned int i = 0; i<refund_len; i++)
+      {
+        struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
+        const json_t *jrefund = json_array_get (refunds,
+                                                i);
+        uint32_t exchange_status;
+        int ret;
+        struct GNUNET_JSON_Specification espec[] = {
+          GNUNET_JSON_spec_uint32 ("exchange_status",
+                                   &exchange_status),
+          GNUNET_JSON_spec_end ()
+        };
+
+        if (GNUNET_OK !=
+            GNUNET_JSON_parse (jrefund,
+                               espec,
+                               NULL, NULL))
+        {
+          GNUNET_break_op (0);
+          cb_failure (owgh,
+                      TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
+                      json);
+          TALER_MERCHANT_wallet_order_get_cancel (owgh);
+          return;
+        }
+
+        if (MHD_HTTP_OK == exchange_status)
+        {
+          struct GNUNET_JSON_Specification rspec[] = {
+            GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                         &rd->exchange_sig),
+            GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                         &rd->exchange_pub),
+            GNUNET_JSON_spec_uint64 ("rtransaction_id",
+                                     &rd->rtransaction_id),
+            GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                         &rd->coin_pub),
+            TALER_JSON_spec_amount ("refund_amount",
+                                    &rd->refund_amount),
+            GNUNET_JSON_spec_end ()
+          };
+
+          ret = GNUNET_JSON_parse (jrefund,
+                                   rspec,
+                                   NULL, NULL);
+        }
+        else
+        {
+          struct GNUNET_JSON_Specification rspec[] = {
+            GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                         &rd->coin_pub),
+            GNUNET_JSON_spec_uint64 ("rtransaction_id",
+                                     &rd->rtransaction_id),
+            TALER_JSON_spec_amount ("refund_amount",
+                                    &rd->refund_amount),
+            GNUNET_JSON_spec_end ()
+          };
+
+          ret = GNUNET_JSON_parse (jrefund,
+                                   rspec,
+                                   NULL, NULL);
+          if (GNUNET_OK == ret)
+          {
+            /* parse optional arguments */
+            json_t *jec;
+
+            jec = json_object_get (jrefund,
+                                   "exchange_code");
+            if (NULL != jec)
+            {
+              if (! json_is_integer (jec))
+              {
+                GNUNET_break_op (0);
+                ret = GNUNET_SYSERR;
+              }
+              else
+              {
+                rd->hr.ec = (enum TALER_ErrorCode) json_integer_value (jec);
+              }
+            }
+            rd->hr.reply = json_object_get (jrefund,
+                                            "exchange_reply");
+          }
+        }
+        if (GNUNET_OK != ret)
+        {
+          GNUNET_break_op (0);
+          cb_failure (owgh,
+                      TALER_EC_CHECK_PAYMENT_RESPONSE_MALFORMED,
+                      json);
+          TALER_MERCHANT_wallet_order_get_cancel (owgh);
+          return;
+        }
+        rd->hr.http_status = exchange_status;
+      }
+
+      {
+        struct TALER_MERCHANT_HttpResponse hr = {
+          .reply = json,
+          .http_status = MHD_HTTP_OK
+        };
+
+        owgh->cb (owgh->cb_cls,
+                  &hr,
+                  GNUNET_YES,
+                  refunded ? GNUNET_YES : GNUNET_NO,
+                  refunded ? &refund_amount : NULL,
+                  NULL, /* paid! */
+                  NULL, /* paid! */
+                  &merchant_pub,
+                  refund_len,
+                  rds);
+      }
+    }
+    GNUNET_JSON_parse_free (spec);
+  }
+  TALER_MERCHANT_wallet_order_get_cancel (owgh);
+}
+
+
+/**
+ * Checks the status of a payment.  Issue a GET /orders/$ID request to the
+ * backend.  The @a h_contract serves as identification of the wallet and is
+ * used to authorize the request.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param order_id order id to identify the payment
+ * @param h_contract hash of the contract to authenticate the wallet
+ * @param timeout timeout to use in long polling (how long may the server wait 
to reply
+ *        before generating an unpaid response). Note that this is just 
provided to
+ *        the server, we as client will block until the response comes back or 
until
+ *        #TALER_MERCHANT_order_get_cancel() is called.
+ * @param session_id for which session should the payment status be checked
+ * @param min_refund long poll for the service to approve a refund exceeding 
this value;
+ *        use NULL to not wait for any refund (only for payment). Only makes 
sense
+ *        with a non-zero @a timeout. Can be NULL.
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to @a cb
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_OrderWalletGetHandle *
+TALER_MERCHANT_wallet_order_get (struct GNUNET_CURL_Context *ctx,
+                                 const char *backend_url,
+                                 const char *order_id,
+                                 const struct GNUNET_HashCode *h_contract,
+                                 struct GNUNET_TIME_Relative timeout,
+                                 const char *session_id,
+                                 const struct TALER_Amount *min_refund,
+                                 TALER_MERCHANT_OrderWalletGetCallback cb,
+                                 void *cb_cls)
+{
+  struct TALER_MERCHANT_OrderWalletGetHandle *owgh;
+  unsigned long long tms;
+  long tlong;
+
+  GNUNET_assert (NULL != backend_url);
+  GNUNET_assert (NULL != order_id);
+  owgh = GNUNET_new (struct TALER_MERCHANT_OrderWalletGetHandle);
+  owgh->ctx = ctx;
+  owgh->cb = cb;
+  owgh->cb_cls = cb_cls;
+  tms = (unsigned long long) (timeout.rel_value_us
+                              / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+  /* set curl timeout to *our* long poll timeout plus one minute
+     (for network latency and processing delays) */
+  tlong = (long) (GNUNET_TIME_relative_add (timeout,
+                                            GNUNET_TIME_UNIT_MINUTES).
+                  rel_value_us
+                  / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+  {
+    char timeout_ms[32];
+    struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s;
+    char *path;
+
+    GNUNET_CRYPTO_hash_to_enc (h_contract,
+                               &h_contract_s);
+    GNUNET_snprintf (timeout_ms,
+                     sizeof (timeout_ms),
+                     "%llu",
+                     tms);
+    GNUNET_asprintf (&path,
+                     "orders/%s",
+                     order_id);
+    owgh->url = TALER_url_join (backend_url,
+                                path,
+                                "h_contract",
+                                h_contract_s.encoding,
+                                "session_id",
+                                session_id,
+                                (0 != tms)
+                                ? "timeout_ms"
+                                : NULL,
+                                timeout_ms,
+                                (NULL != min_refund)
+                                ? "refund"
+                                : NULL,
+                                (NULL != min_refund)
+                                ? TALER_amount2s (min_refund)
+                                : NULL,
+                                NULL);
+    GNUNET_free (path);
+  }
+  if (NULL == owgh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (owgh);
+    return NULL;
+  }
+
+  {
+    CURL *eh;
+
+    eh = curl_easy_init ();
+    if (NULL == eh)
+    {
+      GNUNET_break (0);
+      GNUNET_free (owgh->url);
+      GNUNET_free (owgh);
+      return NULL;
+    }
+    if (CURLE_OK !=
+        curl_easy_setopt (eh,
+                          CURLOPT_URL,
+                          owgh->url))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      GNUNET_free (owgh->url);
+      GNUNET_free (owgh);
+      return NULL;
+    }
+    if (CURLE_OK !=
+        curl_easy_setopt (eh,
+                          CURLOPT_TIMEOUT_MS,
+                          tlong))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      GNUNET_free (owgh->url);
+      GNUNET_free (owgh);
+      return NULL;
+    }
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Checking order status at %s\n",
+                owgh->url);
+    if (NULL == (owgh->job =
+                   GNUNET_CURL_job_add (ctx,
+                                        eh,
+                                        GNUNET_YES,
+                                        &handle_wallet_get_order_finished,
+                                        owgh)))
+    {
+      GNUNET_break (0);
+      GNUNET_free (owgh->url);
+      GNUNET_free (owgh);
+      return NULL;
+    }
+  }
+  return owgh;
+}
+
+
+/**
+ * Cancel a GET /orders/$ID request.
+ *
+ * @param owgh handle to the request to be canceled
+ */
+void
+TALER_MERCHANT_wallet_order_get_cancel (
+  struct TALER_MERCHANT_OrderWalletGetHandle *owgh)
+{
+  if (NULL != owgh->job)
+  {
+    GNUNET_CURL_job_cancel (owgh->job);
+    owgh->job = NULL;
+  }
+  GNUNET_free (owgh->url);
+  GNUNET_free (owgh);
+}
+
+
+/* end of merchant_api_wallet_get_order.c */
diff --git a/src/lib/merchant_api_tip_query.c 
b/src/lib/merchant_api_wallet_get_tip.c
similarity index 55%
rename from src/lib/merchant_api_tip_query.c
rename to src/lib/merchant_api_wallet_get_tip.c
index 250d26d..f0fae96 100644
--- a/src/lib/merchant_api_tip_query.c
+++ b/src/lib/merchant_api_wallet_get_tip.c
@@ -15,8 +15,8 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/merchant_api_tip_query.c
- * @brief Implementation of the /tip-query request of the merchant's HTTP API
+ * @file lib/merchant_api_wallet_get_tip.c
+ * @brief Implementation of the GET /tips/$TIP_ID request of the merchant's 
HTTP API
  * @author Florian Dold
  */
 #include "platform.h"
@@ -31,9 +31,9 @@
 
 
 /**
- * @brief A handle for tracking /tip-query operations
+ * @brief A handle for tracking /tip-get operations
  */
-struct TALER_MERCHANT_TipQueryOperation
+struct TALER_MERCHANT_TipWalletGetHandle
 {
   /**
    * The url for this request.
@@ -48,7 +48,7 @@ struct TALER_MERCHANT_TipQueryOperation
   /**
    * Function to call with the result.
    */
-  TALER_MERCHANT_TipQueryCallback cb;
+  TALER_MERCHANT_TipWalletGetCallback cb;
 
   /**
    * Closure for @a cb.
@@ -67,16 +67,16 @@ struct TALER_MERCHANT_TipQueryOperation
  * Function called when we're done processing the
  * HTTP /track/transaction request.
  *
- * @param cls the `struct TALER_MERCHANT_TipQueryOperation`
+ * @param cls the `struct TALER_MERCHANT_TipGetHandle`
  * @param response_code HTTP response code, 0 on error
  * @param json response body, NULL if not in JSON
  */
 static void
-handle_tip_query_finished (void *cls,
-                           long response_code,
-                           const void *response)
+handle_wallet_tip_get_finished (void *cls,
+                                long response_code,
+                                const void *response)
 {
-  struct TALER_MERCHANT_TipQueryOperation *tqo = cls;
+  struct TALER_MERCHANT_TipWalletGetHandle *tgh = cls;
   const json_t *json = response;
   struct TALER_MERCHANT_HttpResponse hr = {
     .http_status = (unsigned int) response_code,
@@ -84,26 +84,24 @@ handle_tip_query_finished (void *cls,
   };
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Got /tip-query response with status code %u\n",
+              "Got /tip/$TIP_ID response with status code %u\n",
               (unsigned int) response_code);
 
-  tqo->job = NULL;
+  tgh->job = NULL;
   switch (response_code)
   {
   case MHD_HTTP_OK:
     {
-      struct GNUNET_TIME_Absolute reserve_expiration;
-      struct TALER_Amount amount_authorized;
-      struct TALER_Amount amount_available;
-      struct TALER_Amount amount_picked_up;
-      struct TALER_ReservePublicKeyP reserve_pub;
+      const char *exchange_url;
+      struct TALER_Amount amount_remaining;
+      struct GNUNET_TIME_Absolute expiration;
       struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_fixed_auto ("reserve_pub", &reserve_pub),
-        GNUNET_JSON_spec_absolute_time ("reserve_expiration",
-                                        &reserve_expiration),
-        TALER_JSON_spec_amount ("amount_authorized", &amount_authorized),
-        TALER_JSON_spec_amount ("amount_available", &amount_available),
-        TALER_JSON_spec_amount ("amount_picked_up", &amount_picked_up),
+        GNUNET_JSON_spec_absolute_time ("expiration",
+                                        &expiration),
+        GNUNET_JSON_spec_string ("exchange_url",
+                                 &exchange_url),
+        TALER_JSON_spec_amount ("tip_amount",
+                                &amount_remaining),
         GNUNET_JSON_spec_end ()
       };
 
@@ -117,14 +115,12 @@ handle_tip_query_finished (void *cls,
         hr.ec = TALER_EC_INVALID_RESPONSE;
         break;
       }
-      tqo->cb (tqo->cb_cls,
+      tgh->cb (tgh->cb_cls,
                &hr,
-               reserve_expiration,
-               &reserve_pub,
-               &amount_authorized,
-               &amount_available,
-               &amount_picked_up);
-      TALER_MERCHANT_tip_query_cancel (tqo);
+               expiration,
+               exchange_url,
+               &amount_remaining);
+      TALER_MERCHANT_wallet_tip_get_cancel (tgh);
       return;
     }
   case MHD_HTTP_INTERNAL_SERVER_ERROR:
@@ -150,85 +146,104 @@ handle_tip_query_finished (void *cls,
                 (int) hr.ec);
     break;
   }
-  tqo->cb (tqo->cb_cls,
+  tgh->cb (tgh->cb_cls,
            &hr,
            GNUNET_TIME_UNIT_ZERO_ABS,
            NULL,
-           NULL,
-           NULL,
            NULL);
-  TALER_MERCHANT_tip_query_cancel (tqo);
+  TALER_MERCHANT_wallet_tip_get_cancel (tgh);
 }
 
 
 /**
- * Issue a /tip-query request to the backend.  Informs the backend
+ * Issue a GET /tips/$TIP_ID request to the backend.  Informs the backend
  * that a customer wants to pick up a tip.
  *
  * @param ctx execution context
  * @param backend_url base URL of the merchant backend
+ * @param tip_id which tip should we query
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
  * @return handle for this operation, NULL upon errors
  */
-struct TALER_MERCHANT_TipQueryOperation *
-TALER_MERCHANT_tip_query (struct GNUNET_CURL_Context *ctx,
-                          const char *backend_url,
-                          TALER_MERCHANT_TipQueryCallback query_cb,
-                          void *query_cb_cls)
+struct TALER_MERCHANT_TipWalletGetHandle *
+TALER_MERCHANT_wallet_tip_get (struct GNUNET_CURL_Context *ctx,
+                               const char *backend_url,
+                               const struct GNUNET_HashCode *tip_id,
+                               TALER_MERCHANT_TipWalletGetCallback cb,
+                               void *cb_cls)
 {
-  struct TALER_MERCHANT_TipQueryOperation *tqo;
+  struct TALER_MERCHANT_TipWalletGetHandle *tgh;
   CURL *eh;
 
-  tqo = GNUNET_new (struct TALER_MERCHANT_TipQueryOperation);
-  tqo->ctx = ctx;
-  tqo->cb = query_cb;
-  tqo->cb_cls = query_cb_cls;
-  tqo->url = TALER_url_join (backend_url,
-                             "tip-query",
-                             NULL);
-  if (NULL == tqo->url)
+  tgh = GNUNET_new (struct TALER_MERCHANT_TipWalletGetHandle);
+  tgh->ctx = ctx;
+  tgh->cb = cb;
+  tgh->cb_cls = cb_cls;
+  {
+    char res_str[sizeof (*tip_id) * 2];
+    char arg_str[sizeof (res_str) + 48];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (tip_id,
+                                         sizeof (*tip_id),
+                                         res_str,
+                                         sizeof (res_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "tips/%s",
+                     res_str);
+    tgh->url = TALER_url_join (backend_url,
+                               arg_str,
+                               NULL);
+  }
+
+  if (NULL == tgh->url)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Could not construct request URL.\n");
-    GNUNET_free (tqo);
+    GNUNET_free (tgh);
     return NULL;
   }
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Requesting URL '%s'\n",
-              tqo->url);
+              tgh->url);
 
   eh = curl_easy_init ();
   GNUNET_assert (CURLE_OK ==
                  curl_easy_setopt (eh,
                                    CURLOPT_URL,
-                                   tqo->url));
+                                   tgh->url));
 
-  tqo->job = GNUNET_CURL_job_add (ctx,
+  tgh->job = GNUNET_CURL_job_add (ctx,
                                   eh,
                                   GNUNET_YES,
-                                  &handle_tip_query_finished,
-                                  tqo);
-  return tqo;
+                                  &handle_wallet_tip_get_finished,
+                                  tgh);
+  return tgh;
 }
 
 
 /**
- * Cancel a /tip-query request.  This function cannot be used
+ * Cancel a GET /tips/$TIP_ID request.  This function cannot be used
  * on a request handle if a response is already served for it.
  *
  * @param tqo handle to the operation being cancelled
  */
 void
-TALER_MERCHANT_tip_query_cancel (struct TALER_MERCHANT_TipQueryOperation *tqo)
+TALER_MERCHANT_wallet_tip_get_cancel (
+  struct TALER_MERCHANT_TipWalletGetHandle *tgh)
 {
-  if (NULL != tqo->job)
+  if (NULL != tgh->job)
   {
-    GNUNET_CURL_job_cancel (tqo->job);
-    tqo->job = NULL;
+    GNUNET_CURL_job_cancel (tgh->job);
+    tgh->job = NULL;
   }
-  GNUNET_free (tqo->url);
-  GNUNET_free (tqo);
+  GNUNET_free (tgh->url);
+  GNUNET_free (tgh);
 }
 
 
-/* end of merchant_api_tip_query.c */
+/* end of merchant_api_wallet_get_tip.c */
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
deleted file mode 100644
index 349be0a..0000000
--- a/src/lib/test_merchant_api.c
+++ /dev/null
@@ -1,1010 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2020 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 lib/test_merchant_api.c
- * @brief testcase to test exchange's HTTP API interface
- * @author Sree Harsha Totakura <sreeharsha@totakura.in>
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <taler/taler_util.h>
-#include <taler/taler_signatures.h>
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_json_lib.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include <taler/taler_bank_service.h>
-#include <taler/taler_fakebank_lib.h>
-#include <taler/taler_testing_lib.h>
-#include <taler/taler_error_codes.h>
-#include "taler_merchant_testing_lib.h"
-
-/**
- * Configuration file we use.  One (big) configuration is used
- * for the various components for this test.
- */
-#define CONFIG_FILE "test_merchant_api.conf"
-
-/**
- * Exchange base URL.  Could also be taken from config.
- */
-#define EXCHANGE_URL "http://localhost:8081/";
-
-static const char *pickup_amounts_1[] = {"EUR:5", NULL};
-
-/**
- * Payto URI of the customer (payer).
- */
-static char *payer_payto;
-
-/**
- * Payto URI of the exchange (escrow account).
- */
-static char *exchange_payto;
-
-/**
- * Payto URI of the merchant (receiver).
- */
-static char *merchant_payto;
-
-/**
- * Configuration of the bank.
- */
-static struct TALER_TESTING_BankConfiguration bc;
-
-/**
- * Configuration of the exchange.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * Merchant base URL.
- */
-static char *merchant_url;
-
-/**
- * Merchant process.
- */
-static struct GNUNET_OS_Process *merchantd;
-
-/**
- * Map for #intern()
- */
-static struct GNUNET_CONTAINER_MultiHashMap *interned_strings;
-
-/**
- * Account number of the exchange at the bank.
- */
-#define EXCHANGE_ACCOUNT_NAME "2"
-
-/**
- * Account number of some user.
- */
-#define USER_ACCOUNT_NAME "62"
-
-/**
- * Account number of some other user.
- */
-#define USER_ACCOUNT_NAME2 "63"
-
-/**
- * Account number used by the merchant
- */
-#define MERCHANT_ACCOUNT_NAME "3"
-
-
-/**
- * Execute the taler-exchange-wirewatch command with
- * our configuration file.
- *
- * @param label label to use for the command.
- */
-static struct TALER_TESTING_Command
-cmd_exec_wirewatch (char *label)
-{
-  return TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE);
-}
-
-
-/**
- * Execute the taler-exchange-aggregator, closer and transfer commands with
- * our configuration file.
- *
- * @param label label to use for the command.
- */
-#define CMD_EXEC_AGGREGATOR(label) \
-  TALER_TESTING_cmd_exec_aggregator (label "-aggregator", CONFIG_FILE), \
-  TALER_TESTING_cmd_exec_transfer (label "-transfer", CONFIG_FILE)
-
-
-/**
- * Run wire transfer of funds from some user's account to the
- * exchange.
- *
- * @param label label to use for the command.
- * @param amount amount to transfer, i.e. "EUR:1"
- * @param url exchange_url
- */
-static struct TALER_TESTING_Command
-cmd_transfer_to_exchange (const char *label,
-                          const char *amount)
-{
-  return TALER_TESTING_cmd_admin_add_incoming (label,
-                                               amount,
-                                               &bc.exchange_auth,
-                                               payer_payto);
-}
-
-
-static const char *
-intern (const char *str)
-{
-  struct GNUNET_HashCode hash;
-  const char *hs;
-
-  if (NULL == interned_strings)
-    interned_strings = GNUNET_CONTAINER_multihashmap_create (32, GNUNET_NO);
-  GNUNET_assert (NULL != interned_strings);
-  GNUNET_CRYPTO_hash (str, strlen (str), &hash);
-  hs = GNUNET_CONTAINER_multihashmap_get (interned_strings, &hash);
-  if (NULL != hs)
-    return hs;
-  hs = GNUNET_strdup (str);
-  GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (
-                   interned_strings,
-                   &hash,
-                   (void *) hs,
-                   GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-  return hs;
-}
-
-
-#define BUF_SZ 512
-
-static const char *
-merchant_url_internal (const char *instance_id)
-{
-  char buf[BUF_SZ];
-
-  if (NULL == instance_id)
-    GNUNET_snprintf (buf,
-                     BUF_SZ,
-                     "%s",
-                     merchant_url);
-  else
-    GNUNET_snprintf (buf,
-                     BUF_SZ,
-                     "%sinstances/%s/",
-                     merchant_url,
-                     instance_id);
-  return intern (buf);
-}
-
-
-static const char *
-merchant_url_external (const char *instance_id)
-{
-  char buf[BUF_SZ];
-  if (NULL == instance_id)
-    GNUNET_snprintf (buf,
-                     BUF_SZ,
-                     "%spublic/",
-                     merchant_url);
-  else
-    GNUNET_snprintf (buf,
-                     BUF_SZ,
-                     "%spublic/instances/%s/",
-                     merchant_url,
-                     instance_id);
-  return intern (buf);
-}
-
-
-/**
- * Main function that will tell the interpreter what commands to
- * run.
- *
- * @param cls closure
- */
-static void
-run (void *cls,
-     struct TALER_TESTING_Interpreter *is)
-{
-  struct TALER_TESTING_Command pay[] = {
-    /**
-     * Move money to the exchange's bank account.
-     */
-    cmd_transfer_to_exchange ("create-reserve-1",
-                              "EUR:10.02"),
-    /**
-     * Make a reserve exist,
-     * according to the previous
-     * transfer.
-     *///
-    cmd_exec_wirewatch ("wirewatch-1"),
-    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
-                                                 "EUR:10.02",
-                                                 payer_payto,
-                                                 exchange_payto,
-                                                 "create-reserve-1"),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
-                                       "create-reserve-1",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
-                                       "create-reserve-1",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    /**
-     * Check the reserve is depleted.
-     */
-    TALER_TESTING_cmd_status ("withdraw-status-1",
-                              "create-reserve-1",
-                              "EUR:0",
-                              MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-1",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"1\",\
-        \"refund_deadline\": {\"t_ms\": 0},\
-        \"pay_deadline\": {\"t_ms\": \"never\" },\
-        \"amount\":\"EUR:5.0\",\
-        \"summary\": \"merchant-lib testcase\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:5}\"} ] }"),
-    TALER_TESTING_cmd_check_payment ("check-payment-1",
-                                     merchant_url,
-                                     MHD_HTTP_OK,
-                                     "create-proposal-1",
-                                     GNUNET_NO),
-    TALER_TESTING_cmd_poll_payment_start ("poll-payment-1",
-                                          merchant_url,
-                                          "create-proposal-1",
-                                          NULL,
-                                          GNUNET_TIME_UNIT_MILLISECONDS),
-    TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-conclude-1",
-                                             MHD_HTTP_OK,
-                                             "poll-payment-1",
-                                             GNUNET_NO),
-    TALER_TESTING_cmd_poll_payment_start ("poll-payment-2",
-                                          merchant_url,
-                                          "create-proposal-1",
-                                          NULL,
-                                          GNUNET_TIME_UNIT_MINUTES),
-    TALER_TESTING_cmd_check_payment_start ("check-payment-2",
-                                           merchant_url,
-                                           "create-proposal-1",
-                                           GNUNET_TIME_UNIT_MINUTES),
-    TALER_TESTING_cmd_pay ("deposit-simple",
-                           merchant_url,
-                           MHD_HTTP_OK,
-                           "create-proposal-1",
-                           "withdraw-coin-1",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-conclude-2",
-                                             MHD_HTTP_OK,
-                                             "poll-payment-2",
-                                             GNUNET_YES),
-    TALER_TESTING_cmd_check_payment_conclude ("check-payment-conclude-2",
-                                              MHD_HTTP_OK,
-                                              "check-payment-2",
-                                              GNUNET_YES),
-    TALER_TESTING_cmd_pay_abort ("pay-abort-2",
-                                 merchant_url,
-                                 "deposit-simple",
-                                 MHD_HTTP_FORBIDDEN),
-    TALER_TESTING_cmd_pay ("replay-simple",
-                           merchant_url,
-                           MHD_HTTP_OK,
-                           "create-proposal-1",
-                           "withdraw-coin-1",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-1"),
-    CMD_EXEC_AGGREGATOR ("run-aggregator"),
-    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-498c",
-                                           EXCHANGE_URL,
-                                           "EUR:4.98",
-                                           exchange_payto,
-                                           merchant_payto),
-    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"),
-    TALER_TESTING_cmd_end ()
-  };
-
-  struct TALER_TESTING_Command double_spending[] = {
-    TALER_TESTING_cmd_proposal ("create-proposal-2",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"2\",\
-        \"refund_deadline\": {\"t_ms\": 0},\
-        \"pay_deadline\": {\"t_ms\": \"never\" },\
-        \"amount\":\"EUR:5.0\",\
-        \"summary\": \"useful product\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:5}\"} ] }"),
-    TALER_TESTING_cmd_proposal_lookup ("fetch-proposal-2",
-                                       merchant_url,
-                                       MHD_HTTP_OK,
-                                       "create-proposal-2",
-                                       NULL),
-    TALER_TESTING_cmd_pay ("deposit-double-2",
-                           merchant_url,
-                           MHD_HTTP_CONFLICT,
-                           "create-proposal-2",
-                           "withdraw-coin-1",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    TALER_TESTING_cmd_history ("history-0",
-                               merchant_url,
-                               MHD_HTTP_OK,
-                               /**
-                                * all records to be returned; setting date as 
0 lets the
-                                * interpreter set it as 'now' + one hour 
delta, just to
-                                * make sure it surpasses the proposal's 
timestamp.
-                                */GNUNET_TIME_UNIT_ZERO_ABS,
-                               /**
-                                * We only expect ONE result 
(create-proposal-1) to be
-                                * included in /history response, because 
create-proposal-3
-                                * did NOT go through because of double 
spending.
-                                */1, // nresult
-                               10, // start
-                               -10), // nrows
-    TALER_TESTING_cmd_end ()
-  };
-  struct TALER_TESTING_Command track[] = {
-    TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-1",
-                                                  merchant_url,
-                                                  MHD_HTTP_OK,
-                                                  "deposit-simple"),
-    TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-1",
-                                               merchant_url,
-                                               MHD_HTTP_OK,
-                                               "check_bank_transfer-498c"),
-    TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-again",
-                                               merchant_url,
-                                               MHD_HTTP_OK,
-                                               "check_bank_transfer-498c"),
-    cmd_transfer_to_exchange ("create-reserve-2",
-                              "EUR:1"),
-    TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-2b",
-                                                   "EUR:4.01",
-                                                   &bc.exchange_auth,
-                                                   payer_payto,
-                                                   "create-reserve-2"),
-    cmd_exec_wirewatch ("wirewatch-2"),
-    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2a",
-                                                 "EUR:1",
-                                                 payer_payto,
-                                                 exchange_payto,
-                                                 "create-reserve-2"),
-    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2b",
-                                                 "EUR:4.01",
-                                                 payer_payto,
-                                                 exchange_payto,
-                                                 "create-reserve-2"),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
-                                       "create-reserve-2",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_pay ("deposit-simple-2",
-                           merchant_url,
-                           MHD_HTTP_OK,
-                           "create-proposal-2",
-                           "withdraw-coin-2",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    CMD_EXEC_AGGREGATOR ("run-aggregator-2"),
-    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-498c-2",
-                                           EXCHANGE_URL,
-                                           "EUR:4.98",
-                                           exchange_payto,
-                                           merchant_payto),
-    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
-    TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-2",
-                                               merchant_url,
-                                               MHD_HTTP_OK,
-                                               "check_bank_transfer-498c-2"),
-    TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-2-again",
-                                               merchant_url,
-                                               MHD_HTTP_OK,
-                                               "check_bank_transfer-498c-2"),
-    TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-2",
-                                                  merchant_url,
-                                                  MHD_HTTP_OK,
-                                                  "deposit-simple-2"),
-    TALER_TESTING_cmd_history ("history-1",
-                               merchant_url,
-                               MHD_HTTP_OK,
-                               GNUNET_TIME_UNIT_ZERO_ABS,
-                               /**
-                                * Now we expect BOTH contracts 
(create-proposal-{1,2})
-                                * to be included in /history response, because
-                                * create-proposal-2 has now been correctly 
paid.
-                                */2,
-                               10,
-                               -10),
-    TALER_TESTING_cmd_end ()
-  };
-
-  struct TALER_TESTING_Command refund[] = {
-    cmd_transfer_to_exchange ("create-reserve-1r",
-                              "EUR:10.02"),
-    /**
-     * Make a reserve exist, according to the previous transfer.
-     *///
-    cmd_exec_wirewatch ("wirewatch-1r"),
-    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2r",
-                                                 "EUR:10.02",
-                                                 payer_payto,
-                                                 exchange_payto,
-                                                 "create-reserve-1r"),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1r",
-                                       "create-reserve-1r",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2r",
-                                       "create-reserve-1r",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    /**
-     * Check the reserve is depleted.
-     */
-    TALER_TESTING_cmd_status ("withdraw-status-1r",
-                              "create-reserve-1r",
-                              "EUR:0",
-                              MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-1r",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"1r\",\
-        \"refund_deadline\": {\"t_ms\": 0},\
-        \"pay_deadline\": {\"t_ms\": \"never\" },\
-        \"amount\":\"EUR:5.0\",\
-        \"summary\": \"merchant-lib testcase\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:5}\"} ] }"),
-    TALER_TESTING_cmd_pay ("pay-for-refund-1r",
-                           merchant_url,
-                           MHD_HTTP_OK,
-                           "create-proposal-1r",
-                           "withdraw-coin-1r",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    TALER_TESTING_cmd_poll_payment_start ("poll-payment-refund-1",
-                                          merchant_url,
-                                          "create-proposal-1r",
-                                          "EUR:0.0",
-                                          GNUNET_TIME_UNIT_MINUTES),
-    TALER_TESTING_cmd_refund_increase ("refund-increase-1r",
-                                       merchant_url,
-                                       "refund test",
-                                       "1r", /* order ID */
-                                       "EUR:0.1",
-                                       "EUR:0.01",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-refund-conclude-1",
-                                             MHD_HTTP_OK,
-                                             "poll-payment-refund-1",
-                                             GNUNET_YES),
-    /* Ordinary refund.  */
-    TALER_TESTING_cmd_refund_lookup ("refund-lookup-1r",
-                                     merchant_url,
-                                     "refund-increase-1r",
-                                     "pay-for-refund-1r",
-                                     "1r",
-                                     MHD_HTTP_OK),
-    /* Trying to pick up refund from non existent proposal.  */
-    TALER_TESTING_cmd_refund_lookup ("refund-lookup-non-existent",
-                                     merchant_url,
-                                     "refund-increase-1r",
-                                     "deposit-simple",
-                                     "non-existend-id",
-                                     MHD_HTTP_NOT_FOUND),
-
-    /* Test /refund on a contract that was never paid.  */
-    TALER_TESTING_cmd_proposal ("create-proposal-not-to-be-paid",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"1-unpaid\",\
-        \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
-        \"amount\":\"EUR:5.0\",\
-        \"summary\": \"useful product\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:5}\"} ] }"),
-    /* Try to increase a non paid proposal.  */
-    TALER_TESTING_cmd_refund_increase ("refund-increase-unpaid-proposal",
-                                       merchant_url,
-                                       "refund test",
-                                       "1-unpaid",
-                                       "EUR:0.1",
-                                       "EUR:0.01",
-                                       MHD_HTTP_CONFLICT),
-    /* Try to increase a non existent proposal.  */
-    TALER_TESTING_cmd_refund_increase ("refund-increase-unpaid-proposal",
-                                       merchant_url,
-                                       "refund test",
-                                       "non-existent-id",
-                                       "EUR:0.1",
-                                       "EUR:0.01",
-                                       MHD_HTTP_NOT_FOUND),
-    /*
-       The following block will (1) create a new
-       reserve, then (2) a proposal, then (3) pay for
-       it, and finally (4) attempt to pick up a refund
-       from it without any increasing taking place
-       in the first place.
-    *///
-    cmd_transfer_to_exchange ("create-reserve-unincreased-refund",
-                              "EUR:5.01"),
-    cmd_exec_wirewatch ("wirewatch-unincreased-refund"),
-    TALER_TESTING_cmd_check_bank_admin_transfer (
-      "check_bank_transfer-unincreased-refund",
-      "EUR:5.01",
-      payer_payto,
-      exchange_payto,
-      "create-reserve-unincreased-refund"),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unincreased-refund",
-                                       "create-reserve-unincreased-refund",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-unincreased-refund",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"unincreased-proposal\",\
-        \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":\"never\"},\
-        \"amount\":\"EUR:5.0\",\
-        \"summary\": \"merchant-lib testcase\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:5}\"} ] }"),
-    TALER_TESTING_cmd_pay ("pay-unincreased-proposal",
-                           merchant_url,
-                           MHD_HTTP_OK,
-                           "create-proposal-unincreased-refund",
-                           "withdraw-coin-unincreased-refund",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    CMD_EXEC_AGGREGATOR ("run-aggregator-unincreased-refund"),
-    TALER_TESTING_cmd_check_bank_transfer (
-      "check_bank_transfer-paid-unincreased-refund",
-      EXCHANGE_URL,
-      "EUR:9.88", /* '4.98 from above', plus 4.99 from 'pay-for-refund-1r'
-                     and MINUS 0.1 PLUS 0.01 (deposit fee) from 
'refund-increase-1r' */
-      exchange_payto,
-      merchant_payto),
-    /* Actually try to pick up the refund from the "unincreased proposal".  */
-    TALER_TESTING_cmd_refund_lookup_with_amount ("refund-lookup-unincreased",
-                                                 merchant_url,
-                                                 NULL,
-                                                 "pay-unincreased-proposal",
-                                                 "unincreased-proposal",
-                                                 MHD_HTTP_NOT_FOUND,
-                                                 /* If a lookup is attempted
-                                                  * on an unincreased
-                                                  * proposal, the backend will
-                                                  * simply respond with a
-                                                  * empty refunded coin "set",
-                                                  * but the HTTP response code
-                                                  * is 200 OK.  *///
-                                                 "EUR:0"),
-    TALER_TESTING_cmd_end ()
-  };
-
-  struct TALER_TESTING_Command tip[] = {
-    /* Test tipping.  */
-    TALER_TESTING_cmd_admin_add_incoming_with_instance ("create-reserve-tip-1",
-                                                        "EUR:20.04",
-                                                        &bc.exchange_auth,
-                                                        payer_payto,
-                                                        "tip",
-                                                        CONFIG_FILE),
-    cmd_exec_wirewatch ("wirewatch-3"),
-    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-tip-1",
-                                                 "EUR:20.04",
-                                                 payer_payto,
-                                                 exchange_payto,
-                                                 "create-reserve-tip-1"),
-    TALER_TESTING_cmd_tip_authorize ("authorize-tip-1",
-                                     merchant_url_internal ("tip"),
-                                     EXCHANGE_URL,
-                                     MHD_HTTP_OK,
-                                     "tip 1",
-                                     "EUR:5.01"),
-    TALER_TESTING_cmd_tip_authorize ("authorize-tip-2",
-                                     merchant_url_internal ("tip"),
-                                     EXCHANGE_URL,
-                                     MHD_HTTP_OK,
-                                     "tip 2",
-                                     "EUR:5.01"),
-    /* This command tests the authorization of tip
-     * against a reserve that does not exist.  This is
-     * implemented by passing a "tip instance" that
-     * specifies a reserve key that was never used to
-     * actually create a reserve.  *///
-    TALER_TESTING_cmd_tip_authorize_with_ec ("authorize-tip-null",
-                                             merchant_url_internal ("nulltip"),
-                                             EXCHANGE_URL,
-                                             MHD_HTTP_SERVICE_UNAVAILABLE,
-                                             "tip 2",
-                                             "EUR:5.01",
-                                             
TALER_EC_TIP_QUERY_RESERVE_UNKNOWN_TO_EXCHANGE),
-    TALER_TESTING_cmd_tip_query ("query-tip-1",
-                                 merchant_url_internal ("tip"),
-                                 MHD_HTTP_OK),
-    TALER_TESTING_cmd_tip_query_with_amounts ("query-tip-2",
-                                              merchant_url_internal ("tip"),
-                                              MHD_HTTP_OK,
-                                              "EUR:0.0", // picked
-                                              "EUR:10.02", // authorized
-                                              "EUR:20.04"),// available
-    TALER_TESTING_cmd_tip_pickup ("pickup-tip-1",
-                                  merchant_url_external ("tip"),
-                                  MHD_HTTP_OK,
-                                  "authorize-tip-1",
-                                  pickup_amounts_1),
-    TALER_TESTING_cmd_tip_query_with_amounts ("query-tip-3",
-                                              merchant_url_internal ("tip"),
-                                              MHD_HTTP_OK,
-                                              "EUR:5.01", // picked
-                                              NULL, // authorized
-                                              "EUR:15.03"),// available
-    TALER_TESTING_cmd_tip_pickup ("pickup-tip-2",
-                                  merchant_url_external ("tip"),
-                                  MHD_HTTP_OK,
-                                  "authorize-tip-2",
-                                  pickup_amounts_1),
-    TALER_TESTING_cmd_tip_query_with_amounts ("query-tip-4",
-                                              merchant_url_internal ("tip"),
-                                              MHD_HTTP_OK,
-                                              "EUR:10.02", // pick
-                                              "EUR:10.02", // authorized
-                                              "EUR:10.02"), // available
-    TALER_TESTING_cmd_admin_add_incoming_with_instance (
-      "create-reserve-insufficient-funds",
-      "EUR:1.01",
-      &bc.exchange_auth,
-      payer_payto,
-      "dtip",
-      CONFIG_FILE),
-    TALER_TESTING_cmd_check_bank_admin_transfer (
-      "check_bank_transfer-insufficient-tip-funds",
-      "EUR:1.01",
-      payer_payto,
-      exchange_payto,
-      "create-reserve-insufficient-funds"),
-    cmd_exec_wirewatch ("wirewatch-insufficient-tip-funds"),
-    TALER_TESTING_cmd_tip_authorize_with_ec (
-      "authorize-tip-3-insufficient-funds",
-      merchant_url_internal ("dtip"),
-      EXCHANGE_URL,
-      MHD_HTTP_PRECONDITION_FAILED,
-      "tip 3",
-      "EUR:2.02",
-      TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS),
-    TALER_TESTING_cmd_tip_authorize_with_ec 
("authorize-tip-4-unknown-instance",
-                                             merchant_url_internal ("unknown"),
-                                             EXCHANGE_URL,
-                                             MHD_HTTP_NOT_FOUND,
-                                             "tip 4",
-                                             "EUR:5.01",
-                                             TALER_EC_INSTANCE_UNKNOWN),
-    TALER_TESTING_cmd_tip_authorize_with_ec ("authorize-tip-5-notip-instance",
-                                             merchant_url,
-                                             EXCHANGE_URL,
-                                             MHD_HTTP_PRECONDITION_FAILED,
-                                             "tip 5",
-                                             "EUR:5.01",
-                                             
TALER_EC_TIP_AUTHORIZE_INSTANCE_DOES_NOT_TIP),
-    TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-tip-3-too-much",
-                                          merchant_url_external ("tip"),
-                                          MHD_HTTP_CONFLICT,
-                                          "authorize-tip-1",
-                                          pickup_amounts_1,
-                                          TALER_EC_TIP_PICKUP_NO_FUNDS),
-    TALER_TESTING_cmd_tip_authorize_fake ("fake-tip-authorization"),
-    TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-non-existent-id",
-                                          merchant_url_external ("tip"),
-                                          MHD_HTTP_NOT_FOUND,
-                                          "fake-tip-authorization",
-                                          pickup_amounts_1,
-                                          TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN),
-    TALER_TESTING_cmd_proposal ("create-proposal-tip-1",
-                                merchant_url_internal ("tip"),
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"1-tip\",                           \
-        \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999999},\
-        \"amount\":\"EUR:5.0\",\
-        \"summary\": \"useful product\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:5}\"} ] }"),
-    TALER_TESTING_cmd_pay ("deposit-tip-simple",
-                           merchant_url_external ("tip"),
-                           MHD_HTTP_OK,
-                           "create-proposal-tip-1",
-                           "pickup-tip-1",
-                           "EUR:5", // amount + fee
-                           "EUR:4.99", // amount - fee
-                           "EUR:0.01"), // refund fee
-    CMD_EXEC_AGGREGATOR ("aggregator-tip-1"),
-    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-tip-498c",
-                                           EXCHANGE_URL,
-                                           "EUR:4.98",
-                                           exchange_payto,
-                                           merchant_payto),
-    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-at-tips"),
-    TALER_TESTING_cmd_end ()
-  };
-
-  struct TALER_TESTING_Command pay_again[] = {
-    cmd_transfer_to_exchange ("create-reserve-10",
-                              "EUR:10.02"),
-    cmd_exec_wirewatch ("wirewatch-10"),
-    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-10",
-                                                 "EUR:10.02",
-                                                 payer_payto,
-                                                 exchange_payto,
-                                                 "create-reserve-10"),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10a",
-                                       "create-reserve-10",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10b",
-                                       "create-reserve-10",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_status ("withdraw-status-10",
-                              "create-reserve-10",
-                              "EUR:0",
-                              MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-10",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"10\",\
-        \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999999},\
-        \"amount\":\"EUR:10.0\",\
-        \"summary\": \"merchant-lib testcase\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:10}\"} ] }"),
-    TALER_TESTING_cmd_pay ("pay-fail-partial-double-10",
-                           merchant_url,
-                           MHD_HTTP_CONFLICT,
-                           "create-proposal-10",
-                           "withdraw-coin-10a;withdraw-coin-1",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    TALER_TESTING_cmd_pay_again ("pay-again-10",
-                                 merchant_url,
-                                 "pay-fail-partial-double-10",
-                                 "withdraw-coin-10a;withdraw-coin-10b",
-                                 "EUR:0.01",
-                                 MHD_HTTP_OK),
-    CMD_EXEC_AGGREGATOR ("run-aggregator-10"),
-    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-9.97-10",
-                                           EXCHANGE_URL,
-                                           "EUR:9.97",
-                                           exchange_payto,
-                                           merchant_payto),
-    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-10"),
-    TALER_TESTING_cmd_end ()
-  };
-
-  struct TALER_TESTING_Command pay_abort[] = {
-    cmd_transfer_to_exchange ("create-reserve-11",
-                              "EUR:10.02"),
-    cmd_exec_wirewatch ("wirewatch-11"),
-    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-11",
-                                                 "EUR:10.02",
-                                                 payer_payto,
-                                                 exchange_payto,
-                                                 "create-reserve-11"),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11a",
-                                       "create-reserve-11",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11b",
-                                       "create-reserve-11",
-                                       "EUR:5",
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_status ("withdraw-status-11",
-                              "create-reserve-11",
-                              "EUR:0",
-                              MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-11",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
-        \"order_id\":\"11\",\
-        \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999999},\
-        \"amount\":\"EUR:10.0\",\
-        \"summary\": \"merchant-lib testcase\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{EUR:10}\"} ] }"),
-    TALER_TESTING_cmd_pay ("pay-fail-partial-double-11-good",
-                           merchant_url,
-                           MHD_HTTP_NOT_ACCEPTABLE,
-                           "create-proposal-11",
-                           "withdraw-coin-11a",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    TALER_TESTING_cmd_pay ("pay-fail-partial-double-11-bad",
-                           merchant_url,
-                           MHD_HTTP_CONFLICT,
-                           "create-proposal-11",
-                           "withdraw-coin-1",
-                           "EUR:5",
-                           "EUR:4.99",
-                           "EUR:0.01"),
-    TALER_TESTING_cmd_pay_abort ("pay-abort-11",
-                                 merchant_url,
-                                 "pay-fail-partial-double-11-good",
-                                 MHD_HTTP_OK),
-    TALER_TESTING_cmd_pay_abort_refund ("pay-abort-refund-11",
-                                        /* abort reference */
-                                        "pay-abort-11",
-                                        0,
-                                        "EUR:5",
-                                        "EUR:0.01",
-                                        MHD_HTTP_OK),
-    CMD_EXEC_AGGREGATOR ("run-aggregator-11"),
-    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-11"),
-    TALER_TESTING_cmd_end ()
-  };
-
-  struct TALER_TESTING_Command commands[] = {
-    TALER_TESTING_cmd_config ("config",
-                              merchant_url,
-                              MHD_HTTP_OK),
-    TALER_TESTING_cmd_batch ("pay",
-                             pay),
-    TALER_TESTING_cmd_batch ("double-spending",
-                             double_spending),
-    TALER_TESTING_cmd_batch ("track",
-                             track),
-    TALER_TESTING_cmd_history ("history-2",
-                               merchant_url,
-                               MHD_HTTP_OK,
-                               GNUNET_TIME_absolute_add (
-                                 GNUNET_TIME_UNIT_ZERO_ABS,
-                                 GNUNET_TIME_UNIT_MICROSECONDS),
-                               /* zero results expected, there isn't any row 
with id
-                                * bigger than 10. */
-                               0,
-                               10,
-                               10),
-    TALER_TESTING_cmd_batch ("refund",
-                             refund),
-    TALER_TESTING_cmd_batch ("tip",
-                             tip),
-    TALER_TESTING_cmd_batch ("pay-again",
-                             pay_again),
-    TALER_TESTING_cmd_batch ("pay-abort",
-                             pay_abort),
-    TALER_TESTING_cmd_history_default_start ("history-default-start",
-                                             merchant_url,
-                                             MHD_HTTP_OK,
-                                             GNUNET_TIME_UNIT_ZERO_ABS,
-                                             5, /* Expected number of records 
*/
-                                             -100), /* Delta */
-    /**
-     * End the suite.  Fixme: better to have a label for this
-     * too, as it shows a "(null)" token on logs.
-     */
-    TALER_TESTING_cmd_end ()
-  };
-
-  TALER_TESTING_run_with_fakebank (is,
-                                   commands,
-                                   bc.exchange_auth.wire_gateway_url);
-}
-
-
-int
-main (int argc,
-      char *const *argv)
-{
-  unsigned int ret;
-  /* These environment variables get in the way... */
-  unsetenv ("XDG_DATA_HOME");
-  unsetenv ("XDG_CONFIG_HOME");
-
-  GNUNET_log_setup ("test-merchant-api",
-                    "DEBUG",
-                    NULL);
-  if (GNUNET_OK != TALER_TESTING_prepare_fakebank (CONFIG_FILE,
-                                                   "exchange-account-exchange",
-                                                   &bc))
-    return 77;
-
-  payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME);
-  exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME);
-  merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME);
-
-  if (NULL ==
-      (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE)))
-    return 77;
-
-  TALER_TESTING_cleanup_files (CONFIG_FILE);
-
-  switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
-                                          GNUNET_YES,
-                                          &ec))
-  {
-  case GNUNET_SYSERR:
-    GNUNET_break (0);
-    return 1;
-  case GNUNET_NO:
-    return 77;
-
-  case GNUNET_OK:
-
-    if (NULL == (merchantd =
-                   TALER_TESTING_run_merchant (CONFIG_FILE,
-                                               merchant_url)))
-      return 1;
-
-    ret = TALER_TESTING_setup_with_exchange (&run,
-                                             NULL,
-                                             CONFIG_FILE);
-
-    GNUNET_OS_process_kill (merchantd, SIGTERM);
-    GNUNET_OS_process_wait (merchantd);
-    GNUNET_OS_process_destroy (merchantd);
-    GNUNET_free (merchant_url);
-
-    if (GNUNET_OK != ret)
-      return 1;
-    break;
-  default:
-    GNUNET_break (0);
-    return 1;
-  }
-  return 0;
-}
-
-
-/* end of test_merchant_api.c */
diff --git a/src/lib/testing_api_cmd_check_payment.c 
b/src/lib/testing_api_cmd_check_payment.c
deleted file mode 100644
index 8d59129..0000000
--- a/src/lib/testing_api_cmd_check_payment.c
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2019 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 lib/testing_api_cmd_check_payment.c
- * @brief command to test the /check-payment feature.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a /check-payment conclude CMD.
- */
-struct CheckPaymentConcludeState;
-
-/**
- * State for a /check-payment CMD.
- */
-struct CheckPaymentState
-{
-
-  /**
-   * Operation handle.
-   */
-  struct TALER_MERCHANT_CheckPaymentOperation *cpo;
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * The merchant base URL.
-   */
-  const char *merchant_url;
-
-  /**
-   * Reference to a command that can provide a order id,
-   * typically a /proposal test command.
-   */
-  const char *proposal_reference;
-
-  /**
-   * State for a /check-payment conclude CMD.
-   */
-  struct CheckPaymentConcludeState *cs;
-
-  /**
-   * 0 if long-polling is not desired.
-   */
-  struct GNUNET_TIME_Relative timeout;
-
-  /**
-   * Set to the start time of the @e cpo plus the @e timeout.
-   */
-  struct GNUNET_TIME_Absolute deadline;
-
-  /**
-   * #GNUNET_YES if we expect the proposal was paid, synchronous variant.
-   */
-  int expect_paid;
-
-  /**
-   * #GNUNET_YES if the proposal was paid.
-   */
-  int paid;
-
-  /**
-   * #GNUNET_YES if the proposal was paid and then refunded
-   */
-  int refunded;
-
-  /**
-   * Observed HTTP response status code.
-   */
-  unsigned int http_status;
-
-  /**
-   * Expected HTTP response status code, synchronous variant.
-   */
-  unsigned int expected_http_status;
-
-};
-
-
-/**
- * State for a /check-payment conclude CMD.
- */
-struct CheckPaymentConcludeState
-{
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Reference to a command that can provide a check payment start command.
-   */
-  const char *start_reference;
-
-  /**
-   * Task to wait for the deadline.
-   */
-  struct GNUNET_SCHEDULER_Task *task;
-
-  /**
-   * Expected HTTP response status code.
-   */
-  unsigned int expected_http_status;
-
-  /**
-   * #GNUNET_YES if the proposal was expected to be paid.
-   */
-  int expected_paid;
-
-};
-
-
-/**
- * Free a /check-payment CMD, and possibly cancel a pending
- * operation thereof.
- *
- * @param cls closure
- * @param cmd the command currently getting freed.
- */
-static void
-check_payment_cleanup (void *cls,
-                       const struct TALER_TESTING_Command *cmd)
-{
-  struct CheckPaymentState *cps = cls;
-
-  if (NULL != cps->cpo)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command `%s' was not terminated\n",
-                TALER_TESTING_interpreter_get_current_label (
-                  cps->is));
-    TALER_MERCHANT_check_payment_cancel (cps->cpo);
-  }
-  GNUNET_free (cps);
-}
-
-
-/**
- * Task called when either the timeout for the /check-payment
- * command expired or we got a response.  Checks if the
- * result is what we expected.
- *
- * @param cls a `struct CheckPaymentConcludeState`
- */
-static void
-conclude_task (void *cls)
-{
-  struct CheckPaymentConcludeState *cpc = cls;
-  const struct TALER_TESTING_Command *check_cmd;
-  struct CheckPaymentState *cps;
-  struct GNUNET_TIME_Absolute now;
-
-  cpc->task = NULL;
-  check_cmd =
-    TALER_TESTING_interpreter_lookup_command (cpc->is,
-                                              cpc->start_reference);
-  if (NULL == check_cmd)
-    TALER_TESTING_FAIL (cpc->is);
-  cps = check_cmd->cls;
-  if (NULL != cps->cpo)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected /poll/payment to have completed, but it did not!\n");
-    TALER_TESTING_FAIL (cpc->is);
-  }
-  if (cps->http_status != cpc->expected_http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected HTTP status %u, got %u\n",
-                cpc->expected_http_status,
-                cps->http_status);
-    TALER_TESTING_FAIL (cps->is);
-  }
-  now = GNUNET_TIME_absolute_get ();
-  if ( (GNUNET_NO == cps->paid) &&
-       (GNUNET_TIME_absolute_add (cps->deadline,
-                                  GNUNET_TIME_UNIT_SECONDS).abs_value_us <
-        now.abs_value_us) )
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected answer to be delayed until %llu, but got response at 
%llu\n",
-                (unsigned long long) cps->deadline.abs_value_us,
-                (unsigned long long) now.abs_value_us);
-    TALER_TESTING_FAIL (cps->is);
-  }
-  if (cps->paid != cpc->expected_paid)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected paid status %u, got %u\n",
-                cpc->expected_paid,
-                cps->paid);
-    TALER_TESTING_FAIL (cps->is);
-  }
-  TALER_TESTING_interpreter_next (cps->is);
-}
-
-
-/**
- * Callback for a /check-payment request.
- *
- * @param cls closure.
- * @param hr HTTP response we got
- * @param paid #GNUNET_YES (#GNUNET_NO) if the contract was paid
- *        (not paid).
- * @param refunded #GNUNET_YES (#GNUNET_NO) if the contract was
- *        refunded (not refunded).
- * @param refund_amount the amount that was refunded to this
- *        contract.
- * @param taler_pay_uri the URI that instructs the wallets to process
- *                      the payment
- */
-static void
-check_payment_cb (void *cls,
-                  const struct TALER_MERCHANT_HttpResponse *hr,
-                  int paid,
-                  int refunded,
-                  struct TALER_Amount *refund_amount,
-                  const char *taler_pay_uri)
-{
-  struct CheckPaymentState *cps = cls;
-
-  cps->cpo = NULL;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "check payment (%s): expected paid: %d, paid: %d, url: %s\n",
-              TALER_TESTING_interpreter_get_current_label (cps->is),
-              cps->expect_paid,
-              paid,
-              taler_pay_uri);
-  cps->paid = paid;
-  cps->http_status = hr->http_status;
-  cps->refunded = refunded;
-  if (0 == cps->timeout.rel_value_us)
-  {
-    /* synchronous variant */
-    if (paid != cps->expect_paid)
-      TALER_TESTING_FAIL (cps->is);
-    if (cps->expected_http_status != hr->http_status)
-      TALER_TESTING_FAIL (cps->is);
-    TALER_TESTING_interpreter_next (cps->is);
-  }
-  else
-  {
-    /* asynchronous variant */
-    if (NULL != cps->cs)
-    {
-      GNUNET_SCHEDULER_cancel (cps->cs->task);
-      cps->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
-                                                cps->cs);
-    }
-  }
-}
-
-
-/**
- * Run a /check-payment CMD.
- *
- * @param cmd the command currently being run.
- * @param cls closure.
- * @param is interpreter state.
- */
-static void
-check_payment_run (void *cls,
-                   const struct TALER_TESTING_Command *cmd,
-                   struct TALER_TESTING_Interpreter *is)
-{
-  struct CheckPaymentState *cps = cls;
-  const struct TALER_TESTING_Command *proposal_cmd;
-  const char *order_id;
-
-  cps->is = is;
-  proposal_cmd
-    = TALER_TESTING_interpreter_lookup_command (is,
-                                                cps->proposal_reference);
-  if (NULL == proposal_cmd)
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK != TALER_TESTING_get_trait_order_id (
-        proposal_cmd, 0, &order_id))
-    TALER_TESTING_FAIL (is);
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Checking for order id `%s'\n",
-              order_id);
-  cps->cpo = TALER_MERCHANT_check_payment (is->ctx,
-                                           cps->merchant_url,
-                                           order_id,
-                                           NULL,
-                                           cps->timeout,
-                                           &check_payment_cb,
-                                           cps);
-  GNUNET_assert (NULL != cps->cpo);
-  if (0 != cps->timeout.rel_value_us)
-    TALER_TESTING_interpreter_next (cps->is);
-}
-
-
-/**
- * Make a "check payment" test command.
- *
- * @param label command label.
- * @param merchant_url merchant base url
- * @param http_status expected HTTP response code.
- * @param proposal_reference the proposal whose payment status
- *        is going to be checked.
- * @param expect_paid #GNUNET_YES if we expect the proposal to be
- *        paid, #GNUNET_NO otherwise.
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_payment (const char *label,
-                                 const char *merchant_url,
-                                 unsigned int http_status,
-                                 const char *proposal_reference,
-                                 unsigned int expect_paid)
-{
-  struct CheckPaymentState *cps;
-
-  cps = GNUNET_new (struct CheckPaymentState);
-  cps->expected_http_status = http_status;
-  cps->proposal_reference = proposal_reference;
-  cps->expect_paid = expect_paid;
-  cps->merchant_url = merchant_url;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = cps,
-      .label = label,
-      .run = &check_payment_run,
-      .cleanup = &check_payment_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/**
- * Make a "check payment" test command with long polling support.
- *
- * @param label command label.
- * @param merchant_url merchant base url
- * @param proposal_reference the proposal whose payment status
- *        is going to be checked.
- * @param timeout how long to wait during long polling for the reply
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_payment_start (const char *label,
-                                       const char *merchant_url,
-                                       const char *proposal_reference,
-                                       struct GNUNET_TIME_Relative timeout)
-{
-  struct CheckPaymentState *cps;
-
-  if (0 == timeout.rel_value_us)
-    timeout.rel_value_us = 1; /* 0 reserved for blocking version */
-  cps = GNUNET_new (struct CheckPaymentState);
-  cps->timeout = timeout;
-  cps->proposal_reference = proposal_reference;
-  cps->merchant_url = merchant_url;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = cps,
-      .label = label,
-      .run = &check_payment_run,
-      .cleanup = &check_payment_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/**
- * Free a /check-payment conclusion CMD, and possibly cancel a pending
- * operation thereof.
- *
- * @param cls closure
- * @param cmd the command currently getting freed.
- */
-static void
-check_payment_conclude_cleanup (void *cls,
-                                const struct TALER_TESTING_Command *cmd)
-{
-  struct CheckPaymentConcludeState *cps = cls;
-
-  if (NULL != cps->task)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Command `%s' was not terminated\n",
-                TALER_TESTING_interpreter_get_current_label (
-                  cps->is));
-    GNUNET_SCHEDULER_cancel (cps->task);
-    cps->task = NULL;
-  }
-  GNUNET_free (cps);
-}
-
-
-/**
- * Run a /check-payment conclusion CMD.
- *
- * @param cmd the command currently being run.
- * @param cls closure.
- * @param is interpreter state.
- */
-static void
-check_payment_conclude_run (void *cls,
-                            const struct TALER_TESTING_Command *cmd,
-                            struct TALER_TESTING_Interpreter *is)
-{
-  struct CheckPaymentConcludeState *cpc = cls;
-  const struct TALER_TESTING_Command *check_cmd;
-  struct CheckPaymentState *cps;
-
-  cpc->is = is;
-  check_cmd
-    = TALER_TESTING_interpreter_lookup_command (is,
-                                                cpc->start_reference);
-  if (NULL == check_cmd)
-    TALER_TESTING_FAIL (cpc->is);
-  GNUNET_assert (check_cmd->run == &check_payment_run);
-  cps = check_cmd->cls;
-  if (NULL == cps->cpo)
-    cpc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
-                                          cpc);
-  else
-    cpc->task = GNUNET_SCHEDULER_add_at (cps->deadline,
-                                         &conclude_task,
-                                         cpc);
-}
-
-
-/**
- * Expect completion of a long-polled "check payment" test command.
- *
- * @param label command label.
- * @param check_start_reference payment start operation that should have
- *                   completed
- * @param http_status expected HTTP response code.
- * @param expect_paid #GNUNET_YES if we expect the proposal to be
- *        paid, #GNUNET_NO otherwise.
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_payment_conclude (const char *label,
-                                          unsigned int http_status,
-                                          const char *poll_start_reference,
-                                          unsigned int expect_paid)
-{
-  struct CheckPaymentConcludeState *cps;
-
-  cps = GNUNET_new (struct CheckPaymentConcludeState);
-  cps->start_reference = poll_start_reference;
-  cps->expected_paid = expect_paid;
-  cps->expected_http_status = http_status;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = cps,
-      .label = label,
-      .run = &check_payment_conclude_run,
-      .cleanup = &check_payment_conclude_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_check_payment.c */
diff --git a/src/lib/testing_api_cmd_history.c 
b/src/lib/testing_api_cmd_history.c
deleted file mode 100644
index dabbf3c..0000000
--- a/src/lib/testing_api_cmd_history.c
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_history.c
- * @brief command to test the /history API.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a "history" CMD.
- */
-struct HistoryState
-{
-
-  /**
-   * Expected status code.
-   */
-  unsigned int http_status;
-
-  /**
-   * URL of the merchant backend serving the /history request.
-   */
-  const char *merchant_url;
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Handle to the /history operation.
-   */
-  struct TALER_MERCHANT_HistoryOperation *ho;
-
-  /**
-   * Only history entries younger than this
-   * value will be returned.
-   */
-  struct GNUNET_TIME_Absolute time;
-
-  /**
-   * First row index we want in the results.
-   */
-  unsigned long long start;
-
-  /**
-   * When this flag is GNUNET_YES, then the interpreter
-   * will request /history *omitting* the 'start' URL argument.
-   */
-  int use_default_start;
-
-  /**
-   * How many rows we want the response to contain, at most.
-   */
-  long long nrows;
-
-  /**
-   * Expected number of history entries returned by the
-   * backend.
-   */
-  unsigned int nresult;
-};
-
-
-/**
- * Callback for a /history request; checks that (1) HTTP status
- * is expected, the number of rows returned is expected, and that
- * the rows are sorted from the youngest to the oldest record.
- *
- * @param cls closure
- * @param hr HTTP response we got
- */
-static void
-history_cb (void *cls,
-            const struct TALER_MERCHANT_HttpResponse *hr)
-{
-  struct HistoryState *hs = cls;
-  unsigned int nresult;
-  struct GNUNET_TIME_Absolute last_timestamp;
-  struct GNUNET_TIME_Absolute entry_timestamp;
-  json_t *arr;
-
-  hs->ho = NULL;
-
-  if (hs->http_status != hr->http_status)
-    TALER_TESTING_FAIL (hs->is);
-
-  if (MHD_HTTP_OK != hs->http_status)
-  {
-    /* move on without further checking. */
-    TALER_TESTING_interpreter_next (hs->is);
-    return;
-  }
-
-  arr = json_object_get (hr->reply,
-                         "history");
-  nresult = json_array_size (arr);
-  if (hs->nresult != nresult)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected number of history entries: Got %d, expected %d\n",
-                nresult,
-                hs->nresult);
-    TALER_TESTING_FAIL (hs->is);
-  }
-
-  last_timestamp = GNUNET_TIME_absolute_get ();
-  last_timestamp = GNUNET_TIME_absolute_add (last_timestamp,
-                                             GNUNET_TIME_UNIT_DAYS);
-  {
-    json_t *entry;
-    size_t index;
-    json_array_foreach (arr, index, entry)
-    {
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_absolute_time ("timestamp",
-                                        &entry_timestamp),
-        GNUNET_JSON_spec_end ()
-      };
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (entry,
-                             spec,
-                             NULL, NULL))
-        TALER_TESTING_FAIL (hs->is);
-      if (last_timestamp.abs_value_us < entry_timestamp.abs_value_us)
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "History entries are NOT sorted from younger to older\n");
-        TALER_TESTING_interpreter_fail (hs->is);
-        return;
-      }
-      last_timestamp = entry_timestamp;
-    }
-  }
-  TALER_TESTING_interpreter_next (hs->is);
-}
-
-
-/**
- * Free the state for a "history" CMD, and possibly cancel
- * any pending operation thereof.
- *
- * @param cls closure
- * @param cmd command being freed now.
- */
-static void
-history_cleanup (void *cls,
-                 const struct TALER_TESTING_Command *cmd)
-{
-  struct HistoryState *hs = cls;
-
-  if (NULL != hs->ho)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "/history operation did not complete\n");
-    TALER_MERCHANT_history_cancel (hs->ho);
-  }
-  GNUNET_free (hs);
-}
-
-
-/**
- * Run a "history" CMD.
- *
- * @param cls closure.
- * @param cmd current command.
- * @param is interpreter state.
- */
-static void
-history_run (void *cls,
-             const struct TALER_TESTING_Command *cmd,
-             struct TALER_TESTING_Interpreter *is)
-{
-  struct HistoryState *hs = cls;
-
-  hs->is = is;
-  if (0 == hs->time.abs_value_us)
-  {
-    hs->time = GNUNET_TIME_absolute_add
-                 (GNUNET_TIME_absolute_get (),
-                 GNUNET_TIME_UNIT_HOURS);
-    GNUNET_TIME_round_abs (&hs->time);
-  }
-
-  switch (hs->use_default_start)
-  {
-  case GNUNET_YES:
-    hs->ho = TALER_MERCHANT_history_default_start
-               (is->ctx,
-               hs->merchant_url,
-               hs->nrows,
-               hs->time,
-               &history_cb,
-               hs);
-    break;
-
-  case GNUNET_NO:
-    hs->ho = TALER_MERCHANT_history (is->ctx,
-                                     hs->merchant_url,
-                                     hs->start,
-                                     hs->nrows,
-                                     hs->time,
-                                     &history_cb,
-                                     hs);
-    break;
-  default:
-    TALER_LOG_ERROR ("Bad value for 'use_default_start'\n");
-    TALER_TESTING_FAIL (is);
-  }
-
-  if (NULL == hs->ho)
-    TALER_TESTING_FAIL (is);
-}
-
-
-/**
- * Make a "history" command.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        request.
- * @param ctx CURL context.
- * @param http_status expected HTTP response code
- * @param time limit towards the past for the history
- *        records we want returned.
- * @param nresult how many results are expected
- * @param start first row id we want in the result.
- * @param use_default_start if GNUNET_YES, then it will
- *        use the API call that requests /history omitting
- *        the 'start' argument.  This makes easier to test
- *        the server default behaviour.
- * @param nrows how many row we want to receive, at most.
- */
-static struct TALER_TESTING_Command
-cmd_history2 (const char *label,
-              const char *merchant_url,
-              unsigned int http_status,
-              struct GNUNET_TIME_Absolute time,
-              unsigned int nresult,
-              unsigned long long start,
-              int use_default_start,
-              long long nrows)
-{
-  struct HistoryState *hs;
-
-  hs = GNUNET_new (struct HistoryState);
-  hs->http_status = http_status;
-  hs->time = time;
-  hs->nresult = nresult;
-  hs->start = start;
-  hs->nrows = nrows;
-  hs->merchant_url = merchant_url;
-  hs->use_default_start = use_default_start;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = hs,
-      .label = label,
-      .run = &history_run,
-      .cleanup = &history_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/**
- * Make a "history" command.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        request.
- * @param ctx CURL context.
- * @param http_status expected HTTP response code
- * @param time limit towards the past for the history
- *        records we want returned.
- * @param nresult how many results are expected
- * @param nrows how many row we want to receive, at most.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_history_default_start
-  (const char *label,
-  const char *merchant_url,
-  unsigned int http_status,
-  struct GNUNET_TIME_Absolute time,
-  unsigned int nresult,
-  long long nrows)
-{
-  return cmd_history2 (label,
-                       merchant_url,
-                       http_status,
-                       time,
-                       nresult,
-                       -1, /* ignored */
-                       GNUNET_YES,
-                       nrows);
-}
-
-
-/**
- * Make a "history" command.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        request.
- * @param ctx CURL context.
- * @param http_status expected HTTP response code
- * @param time limit towards the past for the history
- *        records we want returned.
- * @param nresult how many results are expected
- * @param start first row id we want in the result.
- * @param nrows how many row we want to receive, at most.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_history (const char *label,
-                           const char *merchant_url,
-                           unsigned int http_status,
-                           struct GNUNET_TIME_Absolute time,
-                           unsigned int nresult,
-                           unsigned long long start,
-                           long long nrows)
-{
-  return cmd_history2 (label,
-                       merchant_url,
-                       http_status,
-                       time,
-                       nresult,
-                       start,
-                       GNUNET_NO,
-                       nrows);
-}
-
-
-/* end of testing_api_cmd_history.c */
diff --git a/src/lib/testing_api_cmd_pay_abort.c 
b/src/lib/testing_api_cmd_pay_abort.c
deleted file mode 100644
index 5911dd1..0000000
--- a/src/lib/testing_api_cmd_pay_abort.c
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018, 2020 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 lib/testing_api_cmd_pay_abort.c
- * @brief command to test the /pay abort feature.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-#define AMOUNT_WITH_FEE 0
-#define AMOUNT_WITHOUT_FEE 1
-#define REFUND_FEE 2
-
-/**
- * State for a "pay abort" CMD.
- */
-struct PayAbortState
-{
-
-  /**
-   * Expected HTTP response code.
-   */
-  unsigned int http_status;
-
-  /**
-   * Reference to the "pay" command to abort.
-   */
-  const char *pay_reference;
-
-  /**
-   * Merchant URL.
-   */
-  const char *merchant_url;
-
-  /**
-   * Handle to a "pay abort" operation.
-   */
-  struct TALER_MERCHANT_Pay *pao;
-
-  /**
-   * Interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * How many refund permissions this CMD got
-   * the right for.  Roughly, there is one refund
-   * permission for one coin.
-   */
-  unsigned int num_refunds;
-
-  /**
-   * The actual refund data.
-   */
-  struct TALER_MERCHANT_RefundEntry *res;
-
-  /**
-   * The contract whose payment is to be aborted.
-   */
-  struct GNUNET_HashCode h_contract;
-
-  /**
-   * Merchant public key.
-   */
-  struct TALER_MerchantPublicKeyP merchant_pub;
-};
-
-
-/**
- * Parse the @a coins specification and grow the @a pc
- * array with the coins found, updating @a npc.
- *
- * @param[in,out] pc pointer to array of coins found
- * @param[in,out] npc length of array at @a pc
- * @param[in] coins string specifying coins to add to @a pc,
- *            clobbered in the process
- * @param is interpreter state
- * @param amount_with_fee total amount to be paid for a contract.
- * @param amount_without_fee to be removed, there is no
- *        per-contract fee, only per-coin exists.
- * @param refund_fee per-contract? per-coin?
- * @return #GNUNET_OK on success
- */
-static int
-build_coins (struct TALER_MERCHANT_PayCoin **pc,
-             unsigned int *npc,
-             char *coins,
-             struct TALER_TESTING_Interpreter *is,
-             const char *amount_with_fee,
-             const char *amount_without_fee,
-             const char *refund_fee)
-{
-  char *token;
-
-  for (token = strtok (coins, ";");
-       NULL != token;
-       token = strtok (NULL, ";"))
-  {
-    const struct TALER_TESTING_Command *coin_cmd;
-    char *ctok;
-    unsigned int ci;
-    struct TALER_MERCHANT_PayCoin *icoin;
-    const struct TALER_EXCHANGE_DenomPublicKey *dpk;
-
-    /* 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);
-        return GNUNET_SYSERR;
-      }
-    }
-
-    coin_cmd = TALER_TESTING_interpreter_lookup_command
-                 (is, token);
-
-    if (NULL == coin_cmd)
-    {
-      GNUNET_break (0);
-      return GNUNET_SYSERR;
-    }
-
-    GNUNET_array_grow (*pc,
-                       *npc,
-                       (*npc) + 1);
-
-    icoin = &((*pc)[(*npc) - 1]);
-
-    {
-      const struct TALER_CoinSpendPrivateKeyP *coin_priv;
-      const struct TALER_DenominationSignature *denom_sig;
-      const struct TALER_Amount *denom_value;
-      const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
-
-      GNUNET_assert
-        (GNUNET_OK == TALER_TESTING_get_trait_coin_priv
-          (coin_cmd, 0, &coin_priv));
-
-      GNUNET_assert
-        (GNUNET_OK == TALER_TESTING_get_trait_denom_pub
-          (coin_cmd, 0, &denom_pub));
-
-      GNUNET_assert
-        (GNUNET_OK == TALER_TESTING_get_trait_denom_sig
-          (coin_cmd, 0, &denom_sig));
-
-      GNUNET_assert
-        (GNUNET_OK == TALER_TESTING_get_trait_amount_obj
-          (coin_cmd, 0, &denom_value));
-
-      icoin->coin_priv = *coin_priv;
-      icoin->denom_pub = denom_pub->key;
-      icoin->denom_sig = *denom_sig;
-      icoin->denom_value = *denom_value;
-      icoin->amount_with_fee = *denom_value;
-    }
-    GNUNET_assert (NULL != (dpk =
-                              TALER_TESTING_find_pk (is->keys,
-                                                     &icoin->denom_value)));
-
-    GNUNET_assert (0 <=
-                   TALER_amount_subtract (&icoin->amount_without_fee,
-                                          &icoin->denom_value,
-                                          &dpk->fee_deposit));
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_TESTING_get_trait_url (coin_cmd,
-                                                
TALER_TESTING_UT_EXCHANGE_BASE_URL,
-                                                &icoin->exchange_url));
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_string_to_amount (refund_fee,
-                                           &icoin->refund_fee));
-  }
-
-  return GNUNET_OK;
-}
-
-
-/**
- * Callback for a "pay abort" operation.  Mainly, check HTTP
- * response code was as expected and stores refund permissions
- * in the state.
- *
- * @param cls closure.
- * @param hr HTTP response
- * @param merchant_pub public key of the merchant refunding the
- *        contract.
- * @param h_contract the contract involved in the refund.
- * @param num_refunds how many refund permissions have been
- *        issued.
- * @param res array containing the refund permissions.
- */
-static void
-pay_abort_cb (void *cls,
-              const struct TALER_MERCHANT_HttpResponse *hr,
-              const struct TALER_MerchantPublicKeyP *merchant_pub,
-              const struct GNUNET_HashCode *h_contract,
-              unsigned int num_refunds,
-              const struct TALER_MERCHANT_RefundEntry *res)
-{
-  struct PayAbortState *pas = cls;
-
-  pas->pao = NULL;
-  if (pas->http_status != hr->http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u (%d) to command %s\n",
-                hr->http_status,
-                (int) hr->ec,
-                TALER_TESTING_interpreter_get_current_label (pas->is));
-    TALER_TESTING_FAIL (pas->is);
-  }
-  if ( (MHD_HTTP_OK == hr->http_status) &&
-       (TALER_EC_NONE == hr->ec) )
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Received %u refunds\n",
-                num_refunds);
-    pas->num_refunds = num_refunds;
-    pas->res = GNUNET_new_array (num_refunds,
-                                 struct TALER_MERCHANT_RefundEntry);
-    memcpy (pas->res,
-            res,
-            num_refunds * sizeof (struct TALER_MERCHANT_RefundEntry));
-    pas->h_contract = *h_contract;
-    pas->merchant_pub = *merchant_pub;
-  }
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Successful pay-abort (HTTP status: %u)\n",
-              hr->http_status);
-  TALER_TESTING_interpreter_next (pas->is);
-}
-
-
-/**
- * Function used by both the "abort" operation.
- * It prepares data and sends the "pay-abort" request to the
- * backend.
- *
- * @param merchant_url base URL of the merchant serving the
- *        request.
- * @param coin_reference reference to the CMD(s) that offer
- *        "coins" traits.  It is possible to give multiple
- *        references by using semicolons to separate them.
- * @param proposal_refere reference to a "proposal" CMD.
- * @param is interpreter state.
- * @param amount_with_fee amount to be paid, including deposit
- *        fee.
- * @param amount_without_fee amount to be paid, without deposit
- *        fee.
- * @param refund_fee refund fee.
- * @param api_func "lib" function that will be called to either
- *        issue a "pay" or "abort" request.
- * @param api_cb callback for @a api_func.
- * @param api_cb_cls closure for @a api_cb
- * @return handle to the operation, NULL if errors occur.
- */
-static struct TALER_MERCHANT_Pay *
-_pay_abort_run (const char *merchant_url,
-                const char *coin_reference,
-                const char *proposal_reference,
-                struct TALER_TESTING_Interpreter *is,
-                const char *amount_with_fee,
-                const char *amount_without_fee,
-                const char *refund_fee,
-                TALER_MERCHANT_PayRefundCallback api_cb,
-                void *api_cb_cls)
-{
-  const struct TALER_TESTING_Command *proposal_cmd;
-  const json_t *contract_terms;
-  const char *order_id;
-  struct GNUNET_TIME_Absolute refund_deadline;
-  struct GNUNET_TIME_Absolute pay_deadline;
-  struct GNUNET_TIME_Absolute timestamp;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  struct GNUNET_HashCode h_wire;
-  const struct GNUNET_HashCode *h_proposal;
-  struct TALER_Amount total_amount;
-  struct TALER_Amount max_fee;
-  const char *error_name;
-  unsigned int error_line;
-  struct TALER_MERCHANT_PayCoin *pay_coins;
-  unsigned int npay_coins;
-  char *cr;
-  struct TALER_MerchantSignatureP *merchant_sig;
-  struct TALER_MERCHANT_Pay *ret;
-
-  proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
-                                                           proposal_reference);
-
-  if (NULL == proposal_cmd)
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_contract_terms (proposal_cmd,
-                                              0,
-                                              &contract_terms))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  {
-    /* Get information that needs to be put verbatim 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 (contract_terms,
-                           spec,
-                           &error_name,
-                           &error_line))
-    {
-      char *js;
-
-      js = json_dumps (contract_terms,
-                       JSON_INDENT (1));
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Parser failed on %s:%u for input `%s'\n",
-                  error_name,
-                  error_line,
-                  js);
-      free (js);
-      GNUNET_break_op (0);
-      return NULL;
-    }
-  }
-
-  cr = GNUNET_strdup (coin_reference);
-  pay_coins = NULL;
-  npay_coins = 0;
-  if (GNUNET_OK !=
-      build_coins (&pay_coins,
-                   &npay_coins,
-                   cr,
-                   is,
-                   amount_with_fee,
-                   amount_without_fee,
-                   refund_fee))
-  {
-    GNUNET_array_grow (pay_coins,
-                       npay_coins,
-                       0);
-    GNUNET_free (cr);
-    GNUNET_break (0);
-    return NULL;
-  }
-
-  GNUNET_free (cr);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
-                                            0,
-                                            &merchant_sig))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
-                                                0,
-                                                &h_proposal))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  ret = TALER_MERCHANT_pay_abort (is->ctx,
-                                  merchant_url,
-                                  h_proposal,
-                                  &total_amount,
-                                  &max_fee,
-                                  &merchant_pub,
-                                  merchant_sig,
-                                  timestamp,
-                                  refund_deadline,
-                                  pay_deadline,
-                                  &h_wire,
-                                  order_id,
-                                  npay_coins,
-                                  pay_coins,
-                                  api_cb,
-                                  api_cb_cls);
-  GNUNET_array_grow (pay_coins,
-                     npay_coins,
-                     0);
-  return ret;
-}
-
-
-/**
- * Free a "pay abort" CMD, and cancel it if need be.
- *
- * @param cls closure.
- * @param cmd command currently being freed.
- */
-static void
-pay_abort_cleanup (void *cls,
-                   const struct TALER_TESTING_Command *cmd)
-{
-  struct PayAbortState *pas = cls;
-
-  if (NULL != pas->pao)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command `%s' did not complete.\n",
-                TALER_TESTING_interpreter_get_current_label (
-                  pas->is));
-    TALER_MERCHANT_pay_cancel (pas->pao);
-  }
-  GNUNET_free_non_null (pas->res);
-  GNUNET_free (pas);
-}
-
-
-/**
- * Run a "pay abort" CMD.
- *
- * @param cls closure
- * @param cmd command being run.
- * @param is interpreter state
- */
-static void
-pay_abort_run (void *cls,
-               const struct TALER_TESTING_Command *cmd,
-               struct TALER_TESTING_Interpreter *is)
-{
-
-  struct PayAbortState *pas = cls;
-  const struct TALER_TESTING_Command *pay_cmd;
-
-  const char *proposal_reference;
-  const char *coin_reference;
-  const char *amount_with_fee;
-  const char *amount_without_fee;
-  const char *refund_fee;
-
-  pas->is = is;
-  pay_cmd = TALER_TESTING_interpreter_lookup_command
-              (is, pas->pay_reference);
-  if (NULL == pay_cmd)
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_proposal_reference
-        (pay_cmd, 0, &proposal_reference))
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_coin_reference
-        (pay_cmd, 0, &coin_reference))
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_string
-        (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_string
-        (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_string
-        (pay_cmd, REFUND_FEE, &refund_fee))
-    TALER_TESTING_FAIL (is);
-
-  if (NULL == (pas->pao = _pay_abort_run (pas->merchant_url,
-                                          coin_reference,
-                                          proposal_reference,
-                                          is,
-                                          amount_with_fee,
-                                          amount_without_fee,
-                                          refund_fee,
-                                          &pay_abort_cb,
-                                          pas)))
-    TALER_TESTING_FAIL (is);
-}
-
-
-/**
- * Offer internal data useful to other commands.
- *
- * @param cls closure
- * @param ret[out] result (could be anything)
- * @param trait name of the trait
- * @param index index number of the object to extract.
- * @return #GNUNET_OK on success
- */
-static int
-pay_abort_traits (void *cls,
-                  const void **ret,
-                  const char *trait,
-                  unsigned int index)
-{
-  struct PayAbortState *pas = cls;
-  struct TALER_TESTING_Trait traits[] = {
-    TALER_TESTING_make_trait_merchant_pub
-      (0, &pas->merchant_pub),
-    TALER_TESTING_make_trait_h_contract_terms
-      (0, &pas->h_contract),
-    TALER_TESTING_make_trait_refund_entry
-      (0, pas->res),
-    TALER_TESTING_make_trait_uint (0, &pas->num_refunds),
-    TALER_TESTING_trait_end ()
-  };
-
-  return TALER_TESTING_get_trait (traits,
-                                  ret,
-                                  trait,
-                                  index);
-}
-
-
-/**
- * Make a "pay abort" test command.
- *
- * @param label command label
- * @param merchant_url merchant base URL
- * @param pay_reference reference to the payment to abort
- * @param http_status expected HTTP response code
- *
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay_abort (const char *label,
-                             const char *merchant_url,
-                             const char *pay_reference,
-                             unsigned int http_status)
-{
-  struct PayAbortState *pas;
-
-  pas = GNUNET_new (struct PayAbortState);
-  pas->http_status = http_status;
-  pas->pay_reference = pay_reference;
-  pas->merchant_url = merchant_url;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = pas,
-      .label = label,
-      .run = &pay_abort_run,
-      .cleanup = &pay_abort_cleanup,
-      .traits = &pay_abort_traits
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_pay_abort.c */
diff --git a/src/lib/testing_api_cmd_pay_abort_refund.c 
b/src/lib/testing_api_cmd_pay_abort_refund.c
deleted file mode 100644
index 918ad75..0000000
--- a/src/lib/testing_api_cmd_pay_abort_refund.c
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_pay_abort_refund.c
- * @brief command to test the pay-abort-refund feature.
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a "pay abort refund" CMD.  This command
- * takes the refund permissions from a "pay abort" CMD,
- * and redeems those at the exchange.
- */
-struct PayAbortRefundState
-{
-
-  /**
-   * "abort" CMD that will provide with refund permissions.
-   */
-  const char *abort_reference;
-
-  /**
-   * Expected number of coins that were refunded.
-   * Only used to counter-check, not to perform any
-   * operation.
-   */
-  unsigned int num_coins;
-
-  /**
-   * The amount to be "withdrawn" from the refund session.
-   */
-  const char *refund_amount;
-
-  /**
-   * The refund fee (charged to the merchant).
-   */
-  const char *refund_fee;
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Handle to the refund operation.
-   */
-  struct TALER_EXCHANGE_RefundHandle *rh;
-
-  /**
-   * Expected HTTP response code.
-   */
-  unsigned int http_status;
-};
-
-
-/**
- * Callback used to work out the response from the exchange
- * to a refund operation.  Currently only checks if the response
- * code is as expected.
- *
- * @param cls closure
- * @param hr HTTP response code details
- * @param sign_key exchange key used to sign @a obj, or NULL
- * @param signature the actual signature, or NULL on error
- */
-static void
-abort_refund_cb (void *cls,
-                 const struct TALER_EXCHANGE_HttpResponse *hr,
-                 const struct TALER_ExchangePublicKeyP *sign_key,
-                 const struct TALER_ExchangeSignatureP *signature)
-{
-  struct PayAbortRefundState *pars = cls;
-
-  pars->rh = NULL;
-  if (pars->http_status != hr->http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u (%d) to command %s\n",
-                hr->http_status,
-                hr->ec,
-                TALER_TESTING_interpreter_get_current_label (pars->is));
-    TALER_TESTING_interpreter_fail (pars->is);
-    return;
-  }
-  TALER_TESTING_interpreter_next (pars->is);
-}
-
-
-/**
- * Free the state of a "pay abort refund" CMD, and possibly
- * cancel a pending operation.
- *
- * @param cls closure.
- * @param cmd the command currently being freed.
- */
-static void
-pay_abort_refund_cleanup (void *cls,
-                          const struct TALER_TESTING_Command *cmd)
-{
-  struct PayAbortRefundState *pars = cls;
-
-  if (NULL != pars->rh)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command `%s' did not complete.\n",
-                TALER_TESTING_interpreter_get_current_label (
-                  pars->is));
-    TALER_EXCHANGE_refund_cancel (pars->rh);
-  }
-  GNUNET_free (pars);
-}
-
-
-/**
- * Run a "pay abort refund" CMD.
- *
- * @param cls closure.
- * @param cmd command currently being run.
- * @param is interpreter state.
- */
-static void
-pay_abort_refund_run (void *cls,
-                      const struct TALER_TESTING_Command *cmd,
-                      struct TALER_TESTING_Interpreter *is)
-{
-  struct PayAbortRefundState *pars = cls;
-  struct TALER_Amount refund_fee;
-  struct TALER_Amount refund_amount;
-  const struct TALER_MERCHANT_RefundEntry *refund_entry;
-  const unsigned int *num_refunds;
-  const struct TALER_TESTING_Command *abort_cmd;
-  const struct TALER_MerchantPublicKeyP *merchant_pub;
-  const struct GNUNET_HashCode *h_contract_terms;
-
-  pars->is = is;
-  if (NULL ==
-      (abort_cmd =
-         TALER_TESTING_interpreter_lookup_command (is,
-                                                   pars->abort_reference)) )
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_uint (abort_cmd,
-                                    0,
-                                    &num_refunds))
-    TALER_TESTING_FAIL (is);
-  if (pars->num_coins >= *num_refunds)
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_h_contract_terms (abort_cmd,
-                                                0,
-                                                &h_contract_terms))
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_merchant_pub (abort_cmd,
-                                            0,
-                                            &merchant_pub))
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_refund_entry (abort_cmd,
-                                            0,
-                                            &refund_entry))
-    TALER_TESTING_FAIL (is);
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (pars->refund_amount,
-                                         &refund_amount));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_string_to_amount (pars->refund_fee,
-                                         &refund_fee));
-  pars->rh = TALER_EXCHANGE_refund2 (is->exchange,
-                                     &refund_amount,
-                                     &refund_fee,
-                                     h_contract_terms,
-                                     &refund_entry->coin_pub,
-                                     refund_entry->rtransaction_id,
-                                     merchant_pub,
-                                     &refund_entry->merchant_sig,
-                                     &abort_refund_cb,
-                                     pars);
-  GNUNET_assert (NULL != pars->rh);
-}
-
-
-/**
- * Make a "pay abort refund" CMD.  This command uses the
- * refund permission from a "pay abort" CMD, and redeems it
- * at the exchange.
- *
- * @param label command label.
- * @param abort_reference reference to the "pay abort" CMD that
- *        will offer the refund permission.
- * @param num_coins how many coins are expected to be refunded.
- * @param refund_amount the amount we are going to redeem as
- *        refund.
- * @param refund_fee the refund fee (merchant pays it)
- * @param http_status expected HTTP response code.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay_abort_refund
-  (const char *label,
-  const char *abort_reference,
-  unsigned int num_coins,
-  const char *refund_amount,
-  const char *refund_fee,
-  unsigned int http_status)
-{
-  struct PayAbortRefundState *pars;
-
-  pars = GNUNET_new (struct PayAbortRefundState);
-  pars->abort_reference = abort_reference;
-  pars->num_coins = num_coins;
-  pars->refund_amount = refund_amount;
-  pars->refund_fee = refund_fee;
-  pars->http_status = http_status;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = pars,
-      .label = label,
-      .run = &pay_abort_refund_run,
-      .cleanup = &pay_abort_refund_cleanup
-    };
-
-    return cmd;
-  }
-}
diff --git a/src/lib/testing_api_cmd_poll_payment.c 
b/src/lib/testing_api_cmd_poll_payment.c
deleted file mode 100644
index 9575e32..0000000
--- a/src/lib/testing_api_cmd_poll_payment.c
+++ /dev/null
@@ -1,491 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_poll_payment.c
- * @brief command to test the /public/poll-payment feature.
- * @author Christian Grothoff
- * @author Marcello Stanisci
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a /poll-payment conclude CMD.
- */
-struct PollPaymentConcludeState;
-
-
-/**
- * State for a /poll-payment start CMD.
- */
-struct PollPaymentStartState
-{
-
-  /**
-   * Operation handle.
-   */
-  struct TALER_MERCHANT_PollPaymentOperation *cpo;
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Reference to a command that can provide a order id,
-   * typically a /proposal test command.
-   */
-  const char *proposal_reference;
-
-  /**
-   * The merchant base URL.
-   */
-  const char *merchant_url;
-
-  /**
-   * Conclude state waiting for completion (if any).
-   */
-  struct PollPaymentConcludeState *cs;
-
-  /**
-   * How long is the long-polling allowed to take?
-   */
-  struct GNUNET_TIME_Relative timeout;
-
-  /**
-   * Set to the start time of the @e cpo plus the @e timeout.
-   */
-  struct GNUNET_TIME_Absolute deadline;
-
-  /**
-   * Amount refunded, set if @e refunded is #GNUNET_YES
-   */
-  struct TALER_Amount refund;
-
-  /**
-   * Refund to wait for, set if @e wait_for_refund is #GNUNET_YES
-   */
-  struct TALER_Amount min_refund;
-
-  /**
-   * Final HTTP response status code.
-   */
-  unsigned int http_status;
-
-  /**
-   * #GNUNET_YES if the proposal was paid.
-   */
-  int paid;
-
-  /**
-   * #GNUNET_YES if the proposal was paid and then refunded
-   */
-  int refunded;
-
-  /**
-   * #GNUNET_YES if we are waiting for a refund.
-   */
-  int wait_for_refund;
-
-};
-
-
-/**
- * State for a /poll-payment conclude CMD.
- */
-struct PollPaymentConcludeState
-{
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Reference to a command that can provide a poll payment start command.
-   */
-  const char *start_reference;
-
-  /**
-   * Task to wait for the deadline.
-   */
-  struct GNUNET_SCHEDULER_Task *task;
-
-  /**
-   * Expected HTTP response status code.
-   */
-  unsigned int expected_http_status;
-
-  /**
-   * #GNUNET_YES if the proposal was expected to be paid.
-   */
-  int expected_paid;
-
-};
-
-
-/**
- * Free a /poll-payment CMD, and possibly cancel a pending
- * operation thereof.
- *
- * @param cls closure
- * @param cmd the command currently getting freed.
- */
-static void
-poll_payment_start_cleanup (void *cls,
-                            const struct TALER_TESTING_Command *cmd)
-{
-  struct PollPaymentStartState *cps = cls;
-
-  if (NULL != cps->cpo)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Command `%s' was not terminated\n",
-                TALER_TESTING_interpreter_get_current_label (
-                  cps->is));
-    TALER_MERCHANT_poll_payment_cancel (cps->cpo);
-  }
-  GNUNET_free (cps);
-}
-
-
-/**
- * Task called when either the timeout for the /poll-payment
- * command expired or we got a response.  Checks if the
- * result is what we expected.
- *
- * @param cls a `struct PollPaymentConcludeState`
- */
-static void
-conclude_task (void *cls)
-{
-  struct PollPaymentConcludeState *ppc = cls;
-  const struct TALER_TESTING_Command *poll_cmd;
-  struct PollPaymentStartState *cps;
-  struct GNUNET_TIME_Absolute now;
-
-  ppc->task = NULL;
-  poll_cmd =
-    TALER_TESTING_interpreter_lookup_command (ppc->is,
-                                              ppc->start_reference);
-  if (NULL == poll_cmd)
-    TALER_TESTING_FAIL (ppc->is);
-  cps = poll_cmd->cls;
-  if (NULL != cps->cpo)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected /poll/payment to have completed, but it did not!\n");
-    TALER_TESTING_FAIL (ppc->is);
-  }
-  if (cps->http_status != ppc->expected_http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected HTTP status %u, got %u\n",
-                ppc->expected_http_status,
-                cps->http_status);
-    TALER_TESTING_FAIL (ppc->is);
-  }
-  now = GNUNET_TIME_absolute_get ();
-  if ( (GNUNET_NO == cps->paid) &&
-       (GNUNET_TIME_absolute_add (cps->deadline,
-                                  GNUNET_TIME_UNIT_SECONDS).abs_value_us <
-        now.abs_value_us) )
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected answer to be delayed until %llu, but got response at 
%llu\n",
-                (unsigned long long) cps->deadline.abs_value_us,
-                (unsigned long long) now.abs_value_us);
-    TALER_TESTING_FAIL (ppc->is);
-  }
-  if (cps->paid != ppc->expected_paid)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Expected paid status %u, got %u\n",
-                ppc->expected_paid,
-                cps->paid);
-    TALER_TESTING_FAIL (ppc->is);
-  }
-  TALER_TESTING_interpreter_next (ppc->is);
-}
-
-
-/**
- * Callback for a /poll-payment request.
- *
- * @param cls closure.
- * @param hr HTTP response we got
- * @param paid #GNUNET_YES (#GNUNET_NO) if the contract was (not) paid
- * @param refunded #GNUNET_YES (#GNUNET_NO) if the contract was
- *        (not) refunded.
- * @param refund_amount the amount that was refunded to this
- *        contract.
- * @param taler_pay_uri the URI that instructs the wallets to process
- *                      the payment
- */
-static void
-poll_payment_cb (void *cls,
-                 const struct TALER_MERCHANT_HttpResponse *hr,
-                 int paid,
-                 int refunded,
-                 struct TALER_Amount *refund_amount,
-                 const char *taler_pay_uri)
-{
-  struct PollPaymentStartState *cps = cls;
-
-  cps->cpo = NULL;
-  if ( (MHD_HTTP_OK != hr->http_status) &&
-       (NULL != hr->reply) )
-  {
-    char *log = json_dumps (hr->reply,
-                            JSON_COMPACT);
-
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Poll payment returned %u: %s\n",
-                hr->http_status,
-                log);
-    free (log);
-  }
-  else
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Poll payment returned %u (%d/%d)\n",
-                hr->http_status,
-                paid,
-                refunded);
-  }
-  cps->paid = paid;
-  cps->http_status = hr->http_status;
-  cps->refunded = refunded;
-  if (GNUNET_YES == refunded)
-    cps->refund = *refund_amount;
-  if (NULL != cps->cs)
-  {
-    GNUNET_SCHEDULER_cancel (cps->cs->task);
-    cps->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
-                                              cps->cs);
-  }
-}
-
-
-/**
- * Run a /poll-payment CMD.
- *
- * @param cmd the command currently being run.
- * @param cls closure.
- * @param is interpreter state.
- */
-static void
-poll_payment_start_run (void *cls,
-                        const struct TALER_TESTING_Command *cmd,
-                        struct TALER_TESTING_Interpreter *is)
-{
-  struct PollPaymentStartState *cps = cls;
-  const struct TALER_TESTING_Command *proposal_cmd;
-  const char *order_id;
-  const struct GNUNET_HashCode *h_contract;
-
-  cps->is = is;
-  proposal_cmd
-    = TALER_TESTING_interpreter_lookup_command (is,
-                                                cps->proposal_reference);
-  if (NULL == proposal_cmd)
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_order_id (proposal_cmd,
-                                        0,
-                                        &order_id))
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
-                                                0,
-                                                &h_contract))
-    TALER_TESTING_FAIL (is);
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Polling for order id `%s'\n",
-              order_id);
-  /* add 1s grace time to timeout */
-  cps->deadline
-    = GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute 
(cps->timeout),
-                                GNUNET_TIME_UNIT_SECONDS);
-  cps->cpo = TALER_MERCHANT_poll_payment (is->ctx,
-                                          cps->merchant_url,
-                                          order_id,
-                                          h_contract,
-                                          NULL, /* session id */
-                                          cps->timeout,
-                                          (GNUNET_YES == cps->wait_for_refund)
-                                          ? &cps->min_refund
-                                          : NULL,
-                                          &poll_payment_cb,
-                                          cps);
-  GNUNET_assert (NULL != cps->cpo);
-  /* We CONTINUE to run the interpreter while the long-polled command
-     completes asynchronously! */
-  TALER_TESTING_interpreter_next (cps->is);
-}
-
-
-/**
- * Start a long-polled "poll-payment" test command.
- *
- * @param label command label.
- * @param merchant_url merchant base url
- * @param proposal_reference the proposal whose payment status
- *        is going to be checked.
- * @param min_refund minimum refund to wait for
- * @param timeout which timeout to use
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_poll_payment_start (const char *label,
-                                      const char *merchant_url,
-                                      const char *proposal_reference,
-                                      const char *min_refund,
-                                      struct GNUNET_TIME_Relative timeout)
-{
-  struct PollPaymentStartState *cps;
-
-  cps = GNUNET_new (struct PollPaymentStartState);
-  cps->proposal_reference = proposal_reference;
-  cps->merchant_url = merchant_url;
-  cps->timeout = timeout;
-  if (NULL != min_refund)
-  {
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_string_to_amount (min_refund,
-                                           &cps->min_refund));
-    cps->wait_for_refund = GNUNET_YES;
-  }
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = cps,
-      .label = label,
-      .run = &poll_payment_start_run,
-      .cleanup = &poll_payment_start_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/**
- * Free a /poll-payment CMD, and possibly cancel a pending
- * operation thereof.
- *
- * @param cls closure
- * @param cmd the command currently getting freed.
- */
-static void
-poll_payment_conclude_cleanup (void *cls,
-                               const struct TALER_TESTING_Command *cmd)
-{
-  struct PollPaymentConcludeState *cps = cls;
-
-  if (NULL != cps->task)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Command `%s' was not terminated\n",
-                TALER_TESTING_interpreter_get_current_label (
-                  cps->is));
-    GNUNET_SCHEDULER_cancel (cps->task);
-    cps->task = NULL;
-  }
-  GNUNET_free (cps);
-}
-
-
-/**
- * Run a /poll-payment CMD.
- *
- * @param cmd the command currently being run.
- * @param cls closure.
- * @param is interpreter state.
- */
-static void
-poll_payment_conclude_run (void *cls,
-                           const struct TALER_TESTING_Command *cmd,
-                           struct TALER_TESTING_Interpreter *is)
-{
-  struct PollPaymentConcludeState *ppc = cls;
-  const struct TALER_TESTING_Command *poll_cmd;
-  struct PollPaymentStartState *cps;
-
-  ppc->is = is;
-  poll_cmd =
-    TALER_TESTING_interpreter_lookup_command (is,
-                                              ppc->start_reference);
-  if (NULL == poll_cmd)
-    TALER_TESTING_FAIL (ppc->is);
-  GNUNET_assert (poll_cmd->run == &poll_payment_start_run);
-  cps = poll_cmd->cls;
-  cps->cs = ppc;
-  if (NULL == cps->cpo)
-    ppc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
-                                          ppc);
-  else
-    ppc->task = GNUNET_SCHEDULER_add_at (cps->deadline,
-                                         &conclude_task,
-                                         ppc);
-}
-
-
-/**
- * Expect completion of a long-polled "poll payment" test command.
- *
- * @param label command label.
- * @param poll_start_reference payment start operation that should have
- *                   completed
- * @param http_status expected HTTP response code.
- * @param expect_paid #GNUNET_YES if we expect the proposal to be
- *        paid, #GNUNET_NO otherwise.
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_poll_payment_conclude (const char *label,
-                                         unsigned int http_status,
-                                         const char *poll_start_reference,
-                                         unsigned int expect_paid)
-{
-  struct PollPaymentConcludeState *cps;
-
-  cps = GNUNET_new (struct PollPaymentConcludeState);
-  cps->start_reference = poll_start_reference;
-  cps->expected_paid = expect_paid;
-  cps->expected_http_status = http_status;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = cps,
-      .label = label,
-      .run = &poll_payment_conclude_run,
-      .cleanup = &poll_payment_conclude_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_poll_payment.c */
diff --git a/src/lib/testing_api_cmd_refund_lookup.c 
b/src/lib/testing_api_cmd_refund_lookup.c
deleted file mode 100644
index 7db933a..0000000
--- a/src/lib/testing_api_cmd_refund_lookup.c
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_refund_lookup.c
- * @brief command to test refunds.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a "refund lookup" CMD.
- */
-struct RefundLookupState
-{
-  /**
-   * Operation handle for a GET /public/refund request.
-   */
-  struct TALER_MERCHANT_RefundLookupOperation *rlo;
-
-  /**
-   * Base URL of the merchant serving the request.
-   */
-  const char *merchant_url;
-
-  /**
-   * Order id to look up.
-   */
-  const char *order_id;
-
-  /**
-   * Reference to a "pay" CMD, used to double-check if
-   * refunded coins were actually spent:
-   */
-  const char *pay_reference;
-
-  /**
-   * Reference to a "refund increase" CMD that offer
-   * the expected amount to be refunded; can be NULL.
-   */
-  const char *increase_reference;
-
-  /**
-   * Expected HTTP response code.
-   */
-  unsigned int http_code;
-
-  /**
-   * Interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Explicit amount to be refunded, must be defined if @a
-   * increase_reference is NULL.
-   */
-  const char *refund_amount;
-};
-
-
-/**
- * Free the state of a "refund lookup" CMD, and
- * possibly cancel a pending "refund lookup" operation.
- *
- * @param cls closure
- * @param cmd command currently being freed.
- */
-static void
-refund_lookup_cleanup (void *cls,
-                       const struct TALER_TESTING_Command *cmd)
-{
-  struct RefundLookupState *rls = cls;
-
-  if (NULL != rls->rlo)
-  {
-    TALER_LOG_WARNING ("Refund-lookup operation"
-                       " did not complete\n");
-    TALER_MERCHANT_refund_lookup_cancel (rls->rlo);
-  }
-  GNUNET_free (rls);
-}
-
-
-/**
- * Process "GET /public/refund" (lookup) response;
- * mainly checking if the refunded amount matches the
- * expectation.
- *
- * @param cls closure
- * @param hr HTTP response we got
- * @param h_contract_terms hash of the contract terms to which the refund is 
applied
- * @param merchant_pub public key of the merchant
- * @param num_details length of the @a details array
- * @param details details about the refund processing
- */
-static void
-refund_lookup_cb (void *cls,
-                  const struct TALER_MERCHANT_HttpResponse *hr,
-                  const struct GNUNET_HashCode *h_contract_terms,
-                  const struct TALER_MerchantPublicKeyP *merchant_pub,
-                  unsigned int num_details,
-                  const struct TALER_MERCHANT_RefundDetail *details)
-{
-  struct RefundLookupState *rls = cls;
-  struct GNUNET_CONTAINER_MultiHashMap *map;
-  const char *coin_reference;
-  const char *icoin_reference;
-  const char *refund_amount;
-  struct TALER_Amount acc;
-  struct TALER_Amount ra;
-
-  rls->rlo = NULL;
-  if (MHD_HTTP_GONE == rls->http_code)
-  {
-    /* special case: GONE is not the top-level code, but expected INSIDE the 
details */
-    if (MHD_HTTP_OK != hr->http_status)
-      TALER_TESTING_FAIL (rls->is);
-    for (unsigned int i = 0; i<num_details; i++)
-      if (MHD_HTTP_GONE != details[i].hr.http_status)
-        TALER_TESTING_FAIL (rls->is);
-    /* all good */
-    TALER_TESTING_interpreter_next (rls->is);
-    return;
-  }
-  if (rls->http_code != hr->http_status)
-    TALER_TESTING_FAIL (rls->is);
-  if (MHD_HTTP_OK != hr->http_status)
-  {
-    TALER_TESTING_interpreter_next (rls->is);
-    return;
-  }
-  map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
-  /* Put in array every refunded coin.  */
-  for (unsigned int i = 0; i<num_details; i++)
-  {
-    struct GNUNET_HashCode h_coin_pub;
-
-    if (MHD_HTTP_OK != details[i].hr.http_status)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Got unexpected status %u/%d for refunded coin %u\n",
-                  details[i].hr.http_status,
-                  (int) details[i].hr.ec,
-                  i);
-      GNUNET_CONTAINER_multihashmap_destroy (map);
-      TALER_TESTING_FAIL (rls->is);
-      return;
-    }
-    TALER_LOG_DEBUG ("Coin %s refund is %s\n",
-                     TALER_B2S (&details[i].coin_pub),
-                     TALER_amount2s (&details[i].refund_amount));
-    GNUNET_CRYPTO_hash (&details[i].coin_pub,
-                        sizeof (struct TALER_CoinSpendPublicKeyP),
-                        &h_coin_pub);
-    if (GNUNET_OK !=
-        GNUNET_CONTAINER_multihashmap_put (
-          map,
-          &h_coin_pub,
-          (void *) &details[i],
-          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
-    {
-      GNUNET_CONTAINER_multihashmap_destroy (map);
-      TALER_TESTING_FAIL (rls->is);
-    }
-  }
-
-  /* Compare spent coins with refunded, and if they match,
-   * increase an accumulator.  */
-  {
-    const struct TALER_TESTING_Command *pay_cmd;
-
-    if (NULL == (pay_cmd = TALER_TESTING_interpreter_lookup_command (
-                   rls->is,
-                   rls->pay_reference)))
-    {
-      GNUNET_CONTAINER_multihashmap_destroy (map);
-      TALER_TESTING_FAIL (rls->is);
-    }
-
-    if (GNUNET_OK !=
-        TALER_TESTING_get_trait_coin_reference (
-          pay_cmd,
-          0,
-          &coin_reference))
-    {
-      GNUNET_CONTAINER_multihashmap_destroy (map);
-      TALER_TESTING_FAIL (rls->is);
-    }
-  }
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_get_zero ("EUR",
-                                        &acc));
-  {
-    char *coin_reference_dup;
-
-    coin_reference_dup = GNUNET_strdup (coin_reference);
-    for (icoin_reference = strtok (coin_reference_dup, ";");
-         NULL != icoin_reference;
-         icoin_reference = strtok (NULL, ";"))
-    {
-      const struct TALER_CoinSpendPrivateKeyP *icoin_priv;
-      struct TALER_CoinSpendPublicKeyP icoin_pub;
-      struct GNUNET_HashCode h_icoin_pub;
-      const struct TALER_MERCHANT_RefundDetail *idetail;
-      const struct TALER_TESTING_Command *icoin_cmd;
-
-      if (NULL ==
-          (icoin_cmd =
-             TALER_TESTING_interpreter_lookup_command (rls->is,
-                                                       icoin_reference)) )
-      {
-        GNUNET_break (0);
-        TALER_LOG_ERROR ("Bad reference `%s'\n",
-                         icoin_reference);
-        TALER_TESTING_interpreter_fail (rls->is);
-        GNUNET_CONTAINER_multihashmap_destroy (map);
-        return;
-      }
-
-      if (GNUNET_OK !=
-          TALER_TESTING_get_trait_coin_priv (icoin_cmd,
-                                             0,
-                                             &icoin_priv))
-      {
-        GNUNET_break (0);
-        TALER_LOG_ERROR ("Command `%s' failed to give coin priv trait\n",
-                         icoin_reference);
-        TALER_TESTING_interpreter_fail (rls->is);
-        GNUNET_CONTAINER_multihashmap_destroy (map);
-        return;
-      }
-      GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv,
-                                          &icoin_pub.eddsa_pub);
-      TALER_LOG_DEBUG ("Looking at coin %s\n",
-                       TALER_B2S (&icoin_pub));
-      GNUNET_CRYPTO_hash (&icoin_pub,
-                          sizeof (struct TALER_CoinSpendPublicKeyP),
-                          &h_icoin_pub);
-
-      idetail = GNUNET_CONTAINER_multihashmap_get (map,
-                                                   &h_icoin_pub);
-
-      /* Can be NULL: not all coins are involved in refund */
-      if (NULL == idetail)
-        continue;
-      TALER_LOG_DEBUG ("Found coin %s refund of %s\n",
-                       TALER_B2S (&idetail->coin_pub),
-                       TALER_amount2s (&idetail->refund_amount));
-      GNUNET_assert (0 <=
-                     TALER_amount_add (&acc,
-                                       &acc,
-                                       &idetail->refund_amount));
-    }
-    GNUNET_free (coin_reference_dup);
-  }
-
-
-  {
-    const struct TALER_TESTING_Command *increase_cmd;
-
-    if (NULL !=
-        (increase_cmd
-           = TALER_TESTING_interpreter_lookup_command (rls->is,
-                                                       
rls->increase_reference)))
-    {
-      if (GNUNET_OK !=
-          TALER_TESTING_get_trait_string (increase_cmd,
-                                          0,
-                                          &refund_amount))
-        TALER_TESTING_FAIL (rls->is);
-
-      if (GNUNET_OK !=
-          TALER_string_to_amount (refund_amount,
-                                  &ra))
-        TALER_TESTING_FAIL (rls->is);
-    }
-    else
-    {
-      GNUNET_assert (NULL != rls->refund_amount);
-
-      if (GNUNET_OK !=
-          TALER_string_to_amount (rls->refund_amount,
-                                  &ra))
-        TALER_TESTING_FAIL (rls->is);
-    }
-  }
-  GNUNET_CONTAINER_multihashmap_destroy (map);
-
-  /* Check that what the backend claims to have been refunded
-   * actually matches _our_ refund expectation.  */
-  if (0 != TALER_amount_cmp (&acc,
-                             &ra))
-  {
-    char *a1;
-
-    a1 = TALER_amount_to_string (&ra);
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Incomplete refund: expected '%s', got '%s'\n",
-                a1,
-                TALER_amount2s (&acc));
-    GNUNET_free (a1);
-    TALER_TESTING_interpreter_fail (rls->is);
-    return;
-  }
-
-  TALER_TESTING_interpreter_next (rls->is);
-}
-
-
-/**
- * Run the "refund lookup" CMD.
- *
- * @param cls closure.
- * @param cmd command being currently run.
- * @param is interpreter state.
- */
-static void
-refund_lookup_run (void *cls,
-                   const struct TALER_TESTING_Command *cmd,
-                   struct TALER_TESTING_Interpreter *is)
-{
-  struct RefundLookupState *rls = cls;
-
-  rls->is = is;
-  rls->rlo = TALER_MERCHANT_refund_lookup (is->ctx,
-                                           rls->merchant_url,
-                                           rls->order_id,
-                                           &refund_lookup_cb,
-                                           rls);
-  GNUNET_assert (NULL != rls->rlo);
-}
-
-
-/**
- * Define a "refund lookup" CMD.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        "refund lookup" request.
- * @param increase_reference reference to a "refund increase" CMD
- *        that will offer the amount to check the looked up refund
- *        against.  Must NOT be NULL.
- * @param pay_reference reference to the "pay" CMD whose coins got
- *        refunded.  It is used to double-check if the refunded
- *        coins were actually spent in the first place.
- * @param order_id order id whose refund status is to be looked up.
- * @param http_code expected HTTP response code.
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_refund_lookup (
-  const char *label,
-  const char *merchant_url,
-  const char *increase_reference,
-  const char *pay_reference,
-  const char *order_id,
-  unsigned int http_code)
-{
-  struct RefundLookupState *rls;
-
-  rls = GNUNET_new (struct RefundLookupState);
-  rls->merchant_url = merchant_url;
-  rls->order_id = order_id;
-  rls->pay_reference = pay_reference;
-  rls->increase_reference = increase_reference;
-  rls->http_code = http_code;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = rls,
-      .label = label,
-      .run = &refund_lookup_run,
-      .cleanup = &refund_lookup_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/**
- * Define a "refund lookup" CMD, equipped with a expected refund
- * amount.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        "refund lookup" request.
- * @param increase_reference reference to a "refund increase" CMD
- *        that will offer the amount to check the looked up refund
- *        against.  Can be NULL, takes precedence over @a
- *        refund_amount.
- * @param pay_reference reference to the "pay" CMD whose coins got
- *        refunded.  It is used to double-check if the refunded
- *        coins were actually spent in the first place.
- * @param order_id order id whose refund status is to be looked up.
- * @param http_code expected HTTP response code.
- * @param refund_amount expected refund amount.  Must be defined
- *        if @a increase_reference is NULL.
- *
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_refund_lookup_with_amount (
-  const char *label,
-  const char *merchant_url,
-  const char *increase_reference,
-  const char *pay_reference,
-  const char *order_id,
-  unsigned int http_code,
-  const char *refund_amount)
-{
-  struct RefundLookupState *rls;
-
-  rls = GNUNET_new (struct RefundLookupState);
-  rls->merchant_url = merchant_url;
-  rls->order_id = order_id;
-  rls->pay_reference = pay_reference;
-  rls->increase_reference = increase_reference;
-  rls->http_code = http_code;
-  rls->refund_amount = refund_amount;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = rls,
-      .label = label,
-      .run = &refund_lookup_run,
-      .cleanup = &refund_lookup_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_refund_lookup.c */
diff --git a/src/lib/testing_api_cmd_rewind.c b/src/lib/testing_api_cmd_rewind.c
deleted file mode 100644
index 2e43b75..0000000
--- a/src/lib/testing_api_cmd_rewind.c
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_rewind.c
- * @brief command to rewind the instruction pointer.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a "rewind" CMD.
- */
-struct RewindIpState
-{
-  /**
-   * Instruction pointer to set into the interpreter.
-   */
-  unsigned int new_ip;
-
-  /**
-   * How many times this set should take place.
-   * However, this value lives at the calling process,
-   * and this CMD is only in charge of checking and
-   * decremeting it.
-   */
-  unsigned int *counter;
-};
-
-
-/**
- * Only defined to respect the API.
- */
-static void
-rewind_ip_cleanup (void *cls,
-                   const struct TALER_TESTING_Command *cmd)
-{
-}
-
-
-/**
- * Run the "rewind" CMD.
- *
- * @param cls closure.
- * @param cmd command being executed now.
- * @param is the interpreter state.
- */
-static void
-rewind_ip_run (void *cls,
-               const struct TALER_TESTING_Command *cmd,
-               struct TALER_TESTING_Interpreter *is)
-{
-  struct RewindIpState *ris = cls;
-
-  if (1 < *ris->counter)
-  {
-    is->ip = ris->new_ip;
-    *ris->counter -= 1;
-  }
-
-  TALER_TESTING_interpreter_next (is);
-}
-
-
-/**
- * Make the instruction pointer point to @a new_ip
- * only if @a counter is greater than zero.
- *
- * @param label command label
- * @param new_ip new instruction pointer's value.  Note that,
- * when the next instruction will be called, the interpreter
- * will increment the ip _anyway_ so this value must be
- * set to the index of the instruction we want to execute next
- * MINUS one.
- * @param counter counts how many times the rewinding has
- * to happen.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_rewind_ip
-  (const char *label,
-  int new_ip,
-  unsigned int *counter)
-{
-  struct RewindIpState *ris;
-
-  ris = GNUNET_new (struct RewindIpState);
-  ris->new_ip = new_ip;
-  ris->counter = counter;
-
-  struct TALER_TESTING_Command cmd = {
-    .cls = ris,
-    .label = label,
-    .run = &rewind_ip_run,
-    .cleanup = &rewind_ip_cleanup
-  };
-
-  return cmd;
-}
diff --git a/src/lib/testing_api_cmd_tip_query.c 
b/src/lib/testing_api_cmd_tip_query.c
deleted file mode 100644
index 70e59c1..0000000
--- a/src/lib/testing_api_cmd_tip_query.c
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_tip_query.c
- * @brief command to test the tipping.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a /tip-query CMD.
- */
-struct TipQueryState
-{
-
-  /**
-   * The merchant base URL.
-   */
-  const char *merchant_url;
-
-  /**
-   * Expected HTTP response code for this CMD.
-   */
-  unsigned int http_status;
-
-  /**
-   * The handle to the current /tip-query request.
-   */
-  struct TALER_MERCHANT_TipQueryOperation *tqo;
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Expected amount to be picked up.
-   */
-  const char *expected_amount_picked_up;
-
-  /**
-   * Expected amount to be tip-authorized.
-   */
-  const char *expected_amount_authorized;
-
-  /**
-   * Amount that is expected to be still available
-   * from the tip reserve.
-   */
-  const char *expected_amount_available;
-};
-
-
-/**
- * Callback to process a GET /tip-query request, it mainly
- * checks that what the backend returned matches the command's
- * expectations.
- *
- * @param cls closure
- * @param hr HTTP response
- * @param reserve_expiration when the tip reserve will expire
- * @param reserve_pub tip reserve public key
- * @param amount_authorized total amount authorized on tip reserve
- * @param amount_available total amount still available on
- *        tip reserve
- * @param amount_picked_up total amount picked up from tip reserve
- */
-static void
-tip_query_cb (void *cls,
-              const struct TALER_MERCHANT_HttpResponse *hr,
-              struct GNUNET_TIME_Absolute reserve_expiration,
-              struct TALER_ReservePublicKeyP *reserve_pub,
-              struct TALER_Amount *amount_authorized,
-              struct TALER_Amount *amount_available,
-              struct TALER_Amount *amount_picked_up)
-{
-  struct TipQueryState *tqs = cls;
-  struct TALER_Amount a;
-
-  tqs->tqo = NULL;
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Tip query callback at command `%s'\n",
-              TALER_TESTING_interpreter_get_current_label (tqs->is));
-  GNUNET_assert (NULL != reserve_pub);
-  GNUNET_assert (NULL != amount_authorized);
-  GNUNET_assert (NULL != amount_available);
-  GNUNET_assert (NULL != amount_picked_up);
-
-  if (tqs->expected_amount_available)
-  {
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_string_to_amount (tqs->expected_amount_available,
-                                           &a));
-    {
-      char *str;
-
-      str = TALER_amount_to_string (amount_available);
-      TALER_LOG_INFO ("expected available %s, actual %s\n",
-                      TALER_amount2s (&a),
-                      str);
-      GNUNET_free (str);
-    }
-    if (0 !=
-        TALER_amount_cmp (amount_available,
-                          &a))
-      TALER_TESTING_FAIL (tqs->is);
-  }
-
-  if (tqs->expected_amount_authorized)
-  {
-    char *str;
-
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_string_to_amount (tqs->expected_amount_authorized,
-                                           &a));
-    str = TALER_amount_to_string (amount_authorized);
-    TALER_LOG_INFO ("expected authorized %s, actual %s\n",
-                    TALER_amount2s (&a),
-                    str);
-    GNUNET_free (str);
-    if (0 !=
-        TALER_amount_cmp (amount_authorized,
-                          &a))
-      TALER_TESTING_FAIL (tqs->is);
-  }
-
-  if (tqs->expected_amount_picked_up)
-  {
-    char *str;
-
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_string_to_amount (tqs->expected_amount_picked_up,
-                                           &a));
-    str = TALER_amount_to_string (amount_picked_up);
-    TALER_LOG_INFO ("expected picked_up %s, actual %s\n",
-                    TALER_amount2s (&a),
-                    str);
-    GNUNET_free (str);
-    if (0 !=
-        TALER_amount_cmp (amount_picked_up,
-                          &a))
-      TALER_TESTING_FAIL (tqs->is);
-  }
-
-  if (tqs->http_status != hr->http_status)
-    TALER_TESTING_FAIL (tqs->is);
-  TALER_TESTING_interpreter_next (tqs->is);
-}
-
-
-/**
- * Free the state from a /tip-query CMD, and possibly cancel
- * a pending /tip-query request.
- *
- * @param cls closure.
- * @param cmd the /tip-query CMD to free.
- */
-static void
-tip_query_cleanup (void *cls,
-                   const struct TALER_TESTING_Command *cmd)
-{
-  struct TipQueryState *tqs = cls;
-
-  if (NULL != tqs->tqo)
-  {
-    TALER_LOG_WARNING ("Tip-query operation"
-                       " did not complete\n");
-    TALER_MERCHANT_tip_query_cancel (tqs->tqo);
-  }
-  GNUNET_free (tqs);
-}
-
-
-/**
- * Run a /tip-query CMD.
- *
- * @param cls closure.
- * @param cmd the current /tip-query CMD.
- * @param is the interpreter state.
- */
-static void
-tip_query_run (void *cls,
-               const struct TALER_TESTING_Command *cmd,
-               struct TALER_TESTING_Interpreter *is)
-{
-  struct TipQueryState *tqs = cls;
-
-  tqs->is = is;
-  tqs->tqo = TALER_MERCHANT_tip_query (is->ctx,
-                                       tqs->merchant_url,
-                                       &tip_query_cb,
-                                       tqs);
-  GNUNET_assert (NULL != tqs->tqo);
-}
-
-
-/**
- * Define a /tip-query CMD equipped with a expected amount.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- *        server the /tip-query request.
- * @param http_status expected HTTP response code for the
- *        /tip-query request.
- * @param expected_amount_picked_up expected amount already
- *        picked up.
- * @param expected_amount_authorized expected amount that was
- *        authorized in the first place.
- * @param expected_amount_available expected amount which is
- *        still available from the tip reserve
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_query_with_amounts (const char *label,
-                                          const char *merchant_url,
-                                          unsigned int http_status,
-                                          const char 
*expected_amount_picked_up,
-                                          const char 
*expected_amount_authorized,
-                                          const char 
*expected_amount_available)
-{
-  struct TipQueryState *tqs;
-
-  tqs = GNUNET_new (struct TipQueryState);
-  tqs->merchant_url = merchant_url;
-  tqs->http_status = http_status;
-  tqs->expected_amount_picked_up = expected_amount_picked_up;
-  tqs->expected_amount_authorized = expected_amount_authorized;
-  tqs->expected_amount_available = expected_amount_available;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = tqs,
-      .label = label,
-      .run = &tip_query_run,
-      .cleanup = &tip_query_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/**
- * Define a /tip-query CMD.
- *
- * @param label the command label
- * @param merchant_url base URL of the merchant which will
- *        server the /tip-query request.
- * @param http_status expected HTTP response code for the
- *        /tip-query request.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_tip_query (const char *label,
-                             const char *merchant_url,
-                             unsigned int http_status)
-{
-  struct TipQueryState *tqs;
-
-  tqs = GNUNET_new (struct TipQueryState);
-  tqs->merchant_url = merchant_url;
-  tqs->http_status = http_status;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = tqs,
-      .label = label,
-      .run = &tip_query_run,
-      .cleanup = &tip_query_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_tip_query.c */
diff --git a/src/lib/testing_api_cmd_track_transaction.c 
b/src/lib/testing_api_cmd_track_transaction.c
deleted file mode 100644
index c945050..0000000
--- a/src/lib/testing_api_cmd_track_transaction.c
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_track_transaction.c
- * @brief command to test /track/transaction
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State for a "track transaction" CMD.
- */
-struct TrackTransactionState
-{
-  /**
-   * Handle for a pending /track/transaction request.
-   */
-  struct TALER_MERCHANT_TrackTransactionHandle *tth;
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Base URL of the merchant serving the request.
-   */
-  const char *merchant_url;
-
-  /**
-   * Expected HTTP response code.
-   */
-  unsigned int http_status;
-
-  /**
-   * Reference to a "pay" CMD, used to get the order
-   * id to issue the track against.
-   */
-  const char *pay_reference;
-
-  /**
-   * Subject line of the wire transfer that paid
-   * the tracked contract back.  WARNING: impredictible
-   * behaviour if _multiple_ wire transfers were
-   * issued to pay this contract back.
-   */
-  const char *wtid_str;
-
-  /**
-   * Binary form of @a wtid_str, expected by other commands
-   * in this form.
-   */
-  struct TALER_WireTransferIdentifierRawP wtid;
-
-  /**
-   * base URL of the exchange that issued (or was supposed to,
-   * in case 202 Accepted was returned) the wire transfer to
-   * pay the tracked contract back.
-   */
-  const char *exchange_url;
-
-};
-
-
-/**
- * Function called with detailed wire transfer data; checks
- * if HTTP response code matches the expectation, and stores
- * in the state what came from the backend.
- *
- * @param cls closure
- * @param hr HTTP response
- */
-static void
-track_transaction_cb (void *cls,
-                      const struct TALER_MERCHANT_HttpResponse *hr)
-{
-  struct TrackTransactionState *tts = cls;
-
-  tts->tth = NULL;
-  if (tts->http_status != hr->http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u (%d) to command %s\n",
-                hr->http_status,
-                (int) hr->ec,
-                TALER_TESTING_interpreter_get_current_label (tts->is));
-    TALER_TESTING_interpreter_fail (tts->is);
-    return;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "/track/transaction, response code: %u\n",
-              hr->http_status);
-  if (MHD_HTTP_OK == hr->http_status)
-  {
-    /* Only storing first element's wtid, as this works around
-     * the disability of the real bank to provide a "bank check"
-     * CMD as the fakebank does.  */
-    json_t *wtid_str;
-    json_t *exchange_url;
-
-    if (NULL == (wtid_str
-                   = json_object_get (json_array_get (hr->reply,
-                                                      0),
-                                      "wtid")))
-    {
-      TALER_TESTING_interpreter_fail (tts->is);
-      return;
-    }
-
-    if (NULL == (exchange_url
-                   = json_object_get (json_array_get (hr->reply,
-                                                      0),
-                                      "exchange")))
-    {
-      TALER_TESTING_interpreter_fail (tts->is);
-      return;
-    }
-
-    tts->exchange_url = GNUNET_strdup (json_string_value (exchange_url));
-    tts->wtid_str = GNUNET_strdup (json_string_value (wtid_str));
-  }
-  TALER_TESTING_interpreter_next (tts->is);
-}
-
-
-/**
- * Run the "track transaction" CMD.
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-track_transaction_run (void *cls,
-                       const struct TALER_TESTING_Command *cmd,
-                       struct TALER_TESTING_Interpreter *is)
-{
-  struct TrackTransactionState *tts = cls;
-  const char *order_id;
-  const struct TALER_TESTING_Command *pay_cmd;
-
-  tts->is = is;
-  if (NULL ==
-      (pay_cmd = TALER_TESTING_interpreter_lookup_command (is,
-                                                           
tts->pay_reference)))
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_order_id (pay_cmd,
-                                        0,
-                                        &order_id))
-    TALER_TESTING_FAIL (is);
-
-  tts->tth = TALER_MERCHANT_track_transaction (is->ctx,
-                                               tts->merchant_url,
-                                               order_id,
-                                               &track_transaction_cb,
-                                               tts);
-  GNUNET_assert (NULL != tts->tth);
-}
-
-
-/**
- * Free the state of a "track transaction" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-track_transaction_cleanup (void *cls,
-                           const struct TALER_TESTING_Command *cmd)
-{
-  struct TrackTransactionState *tts = cls;
-
-  if (NULL != tts->tth)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "/track/transaction (test) operation"
-                " did not complete\n");
-    TALER_MERCHANT_track_transaction_cancel (tts->tth);
-  }
-
-  /* Need to discard 'const' before freeing.  */
-  GNUNET_free_non_null ((char *) tts->exchange_url);
-  GNUNET_free_non_null ((char *) tts->wtid_str);
-  GNUNET_free (tts);
-}
-
-
-/**
- * Offer internal data of a "track transaction" CMD, for
- * other CMDs to use.
- *
- * @param cls closure.
- * @param ret[out] return value.
- * @param trait name of the trait.
- * @param index index of the trait.
- * @return #GNUNET_OK if it is successful.
- */
-static int
-track_transaction_traits (void *cls,
-                          const void **ret,
-                          const char *trait,
-                          unsigned int index)
-{
-  struct TrackTransactionState *tts = cls;
-  struct TALER_WireTransferIdentifierRawP *wtid_ptr;
-
-  if (MHD_HTTP_OK != tts->http_status)
-    return GNUNET_SYSERR;
-  if ( (NULL != tts->wtid_str) &&
-       (GNUNET_OK !=
-        GNUNET_STRINGS_string_to_data (tts->wtid_str,
-                                       strlen (tts->wtid_str),
-                                       &tts->wtid,
-                                       sizeof (struct
-                                               
TALER_WireTransferIdentifierRawP))) )
-    wtid_ptr = NULL;
-  else
-    wtid_ptr = &tts->wtid;
-  {
-    struct TALER_TESTING_Trait traits[] = {
-      TALER_TESTING_make_trait_wtid (0, wtid_ptr),
-      TALER_TESTING_make_trait_url (0, tts->exchange_url),
-      TALER_TESTING_trait_end ()
-    };
-
-    return TALER_TESTING_get_trait (traits,
-                                    ret,
-                                    trait,
-                                    index);
-  }
-}
-
-
-/**
- * Define a "track transaction" CMD.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        /track/transaction request.
- * @param http_status expected HTTP response code.
- * @param pay_reference used to retrieve the order id to track.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_track_transaction (const char *label,
-                                              const char *merchant_url,
-                                              unsigned int http_status,
-                                              const char *pay_reference)
-{
-  struct TrackTransactionState *tts;
-
-  tts = GNUNET_new (struct TrackTransactionState);
-  tts->merchant_url = merchant_url;
-  tts->http_status = http_status;
-  tts->pay_reference = pay_reference;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = tts,
-      .label = label,
-      .run = &track_transaction_run,
-      .cleanup = &track_transaction_cleanup,
-      .traits = &track_transaction_traits
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_track_transaction.c */
diff --git a/src/lib/testing_api_cmd_track_transfer.c 
b/src/lib/testing_api_cmd_track_transfer.c
deleted file mode 100644
index b0dfc47..0000000
--- a/src/lib/testing_api_cmd_track_transfer.c
+++ /dev/null
@@ -1,322 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2018 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 lib/testing_api_cmd_track_transfer.c
- * @brief command to test /track/transfer.
- * @author Marcello Stanisci
- */
-
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State of a "track transfer" CMD.
- */
-struct TrackTransferState
-{
-
-  /**
-   * Handle for a "track transfer" request.
-   */
-  struct TALER_MERCHANT_TrackTransferHandle *tth;
-
-  /**
-   * The interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
-
-  /**
-   * Base URL of the merchant serving the request.
-   */
-  const char *merchant_url;
-
-  /**
-   * Expected HTTP response code.
-   */
-  unsigned int http_status;
-
-  /**
-   * Reference for a "check bank" CMD.  It offers the
-   * WTID to track.
-   */
-  const char *check_bank_reference;
-
-};
-
-
-/**
- * Callback for a /track/transfer operation, only checks if
- * response code is the expected one.
- *
- * @param cls closure for this function
- * @param http_status HTTP response code returned by the server
- * @param ec taler-specific error code
- * @param sign_key exchange key used to sign @a json, or NULL
- * @param json original json reply (may include signatures,
- *        those have then been validated already)
- * @param h_wire hash of the wire transfer address the transfer
- *        went to, or NULL on error
- * @param total_amount total amount of the wire transfer, or NULL
- *        if the exchange could not provide any @a wtid (set only
- *        if @a http_status is #MHD_HTTP_OK)
- * @param details_length length of the @a details array
- * @param details array with details about the combined
- *        transactions
- */
-static void
-track_transfer_cb (void *cls,
-                   const struct TALER_MERCHANT_HttpResponse *hr,
-                   const struct TALER_ExchangePublicKeyP *sign_key,
-                   const struct GNUNET_HashCode *h_wire,
-                   const struct TALER_Amount *total_amount,
-                   unsigned int details_length,
-                   const struct TALER_MERCHANT_TrackTransferDetails *details)
-{
-  /* FIXME, deeper checks should be implemented here. */
-  struct TrackTransferState *tts = cls;
-
-  tts->tth = NULL;
-  if (tts->http_status != hr->http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u (%d) to command %s\n",
-                hr->http_status,
-                (int) hr->ec,
-                TALER_TESTING_interpreter_get_current_label (tts->is));
-    TALER_TESTING_interpreter_fail (tts->is);
-    return;
-  }
-  switch (hr->http_status)
-  {
-  /**
-   * Check that all the deposits sum up to the total
-   * transferred amount.  */
-  case MHD_HTTP_OK:
-    {
-      json_t *deposits;
-      const char *amount_str;
-      struct TALER_Amount total;
-      struct TALER_Amount wire_fee;
-      struct TALER_Amount amount_iter;
-      struct TALER_Amount deposit_fee_iter;
-      struct TALER_Amount sum;
-      size_t index;
-      json_t *value;
-
-      amount_str = json_string_value (json_object_get (hr->reply,
-                                                       "total"));
-      if (GNUNET_OK !=
-          TALER_string_to_amount (amount_str,
-                                  &total))
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Failed to parse amount `%s'\n",
-                    amount_str);
-        TALER_TESTING_FAIL (tts->is);
-        return;
-      }
-      amount_str = json_string_value (json_object_get (hr->reply,
-                                                       "wire_fee"));
-      if (GNUNET_OK !=
-          TALER_string_to_amount (amount_str,
-                                  &wire_fee))
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Failed to parse amount `%s'\n",
-                    amount_str);
-        TALER_TESTING_FAIL (tts->is);
-        return;
-      }
-      GNUNET_assert (GNUNET_OK ==
-                     TALER_amount_get_zero (total.currency,
-                                            &sum));
-      deposits = json_object_get (hr->reply,
-                                  "deposits_sums");
-      json_array_foreach (deposits, index, value)
-      {
-        amount_str = json_string_value (json_object_get (value,
-                                                         "deposit_value"));
-        if (GNUNET_OK !=
-            TALER_string_to_amount (amount_str,
-                                    &amount_iter))
-        {
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Failed to parse amount `%s'\n",
-                      amount_str);
-          TALER_TESTING_FAIL (tts->is);
-          return;
-        }
-        amount_str = json_string_value (json_object_get (value,
-                                                         "deposit_fee"));
-        if (GNUNET_OK !=
-            TALER_string_to_amount (amount_str,
-                                    &deposit_fee_iter))
-        {
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Failed to parse amount `%s'\n",
-                      amount_str);
-          TALER_TESTING_FAIL (tts->is);
-          return;
-        }
-        GNUNET_assert (0 <=
-                       TALER_amount_add (&sum,
-                                         &sum,
-                                         &amount_iter));
-        GNUNET_assert (0 <=
-                       TALER_amount_subtract (&sum,
-                                              &sum,
-                                              &deposit_fee_iter));
-      }
-
-      GNUNET_assert (0 <=
-                     TALER_amount_subtract (&sum,
-                                            &sum,
-                                            &wire_fee));
-      if (0 != TALER_amount_cmp (&sum,
-                                 &total))
-      {
-        GNUNET_break (0);
-        TALER_LOG_ERROR (
-          "Inconsistent amount transferred: Sum %s, claimed %s\n",
-          TALER_amount_to_string (&sum),
-          TALER_amount_to_string (&total));
-        TALER_TESTING_interpreter_fail (tts->is);
-      }
-    }
-    break;
-  default:
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Unhandled HTTP status.\n");
-  }
-  TALER_TESTING_interpreter_next (tts->is);
-}
-
-
-/**
- * Run the "track transfer" CMD.
- *
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-track_transfer_run (void *cls,
-                    const struct TALER_TESTING_Command *cmd,
-                    struct TALER_TESTING_Interpreter *is)
-{
-  struct TrackTransferState *tts = cls;
-  const struct TALER_WireTransferIdentifierRawP *wtid;
-  const struct TALER_TESTING_Command *check_bank_cmd;
-  const char *exchange_url;
-
-  tts->is = is;
-  check_bank_cmd
-    = TALER_TESTING_interpreter_lookup_command (is,
-                                                tts->check_bank_reference);
-  if (NULL == check_bank_cmd)
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_wtid (check_bank_cmd,
-                                    0,
-                                    &wtid))
-    TALER_TESTING_FAIL (is);
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_url (check_bank_cmd,
-                                   TALER_TESTING_UT_EXCHANGE_BASE_URL,
-                                   &exchange_url))
-    TALER_TESTING_FAIL (is);
-  tts->tth = TALER_MERCHANT_track_transfer (is->ctx,
-                                            tts->merchant_url,
-                                            "x-taler-bank",
-                                            wtid,
-                                            exchange_url,
-                                            &track_transfer_cb,
-                                            tts);
-  GNUNET_assert (NULL != tts->tth);
-}
-
-
-/**
- * Free the state of a "track transfer" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-track_transfer_cleanup (void *cls,
-                        const struct TALER_TESTING_Command *cmd)
-{
-  struct TrackTransferState *tts = cls;
-
-  if (NULL != tts->tth)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "/track/transfer (test) operation"
-                " did not complete\n");
-    TALER_MERCHANT_track_transfer_cancel (tts->tth);
-  }
-  GNUNET_free (tts);
-}
-
-
-/**
- * Define a "track transfer" CMD.
- *
- * @param label command label.
- * @param merchant_url base URL of the merchant serving the
- *        /track/transfer request.
- * @param http_status expected HTTP response code.
- * @param check_bank_reference reference to a "check bank" CMD
- *        that will provide the WTID and exchange URL to issue
- *        the track against.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_track_transfer (const char *label,
-                                           const char *merchant_url,
-                                           unsigned int http_status,
-                                           const char *check_bank_reference)
-{
-  struct TrackTransferState *tts;
-
-  tts = GNUNET_new (struct TrackTransferState);
-  tts->merchant_url = merchant_url;
-  tts->http_status = http_status;
-  tts->check_bank_reference = check_bank_reference;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = tts,
-      .label = label,
-      .run = &track_transfer_run,
-      .cleanup = &track_transfer_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_track_transfer.c */
diff --git a/src/merchant-tools/Makefile.am b/src/merchant-tools/Makefile.am
index af51b88..987a089 100644
--- a/src/merchant-tools/Makefile.am
+++ b/src/merchant-tools/Makefile.am
@@ -7,27 +7,16 @@ if USE_COVERAGE
 endif
 
 bin_PROGRAMS = \
+  taler-merchant-benchmark \
   taler-merchant-dbinit \
-  taler-merchant-benchmark
-
-taler_merchant_dbinit_SOURCES = \
-  taler-merchant-dbinit.c
-
-taler_merchant_dbinit_LDADD = \
-  $(LIBGCRYPT_LIBS) \
-  $(top_builddir)/src/backenddb/libtalermerchantdb.la \
-  -lgnunetutil \
-  -ltalerutil \
-  -ltalerpq \
-  $(XLIB)
+  taler-merchant-setup-reserve
 
 taler_merchant_benchmark_SOURCES = \
   taler-merchant-benchmark.c
-
 taler_merchant_benchmark_LDADD = \
   $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
   $(top_srcdir)/src/lib/libtalermerchant.la \
-  $(top_srcdir)/src/lib/libtalermerchanttesting.la \
+  $(top_srcdir)/src/testing/libtalermerchanttesting.la \
   $(LIBGCRYPT_LIBS) \
   -ltalertesting \
   -ltalerfakebank \
@@ -40,3 +29,23 @@ taler_merchant_benchmark_LDADD = \
   -lgnunetutil \
   -ljansson \
   $(XLIB)
+
+taler_merchant_dbinit_SOURCES = \
+  taler-merchant-dbinit.c
+taler_merchant_dbinit_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+  -ltalerutil \
+  -ltalerpq \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_merchant_setup_reserve_SOURCES = \
+  taler-merchant-setup-reserve.c
+taler_merchant_setup_reserve_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/lib/libtalermerchant.la \
+  -ltalerutil \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
diff --git a/src/merchant-tools/taler-merchant-benchmark.c 
b/src/merchant-tools/taler-merchant-benchmark.c
index dc7f933..e410927 100644
--- a/src/merchant-tools/taler-merchant-benchmark.c
+++ b/src/merchant-tools/taler-merchant-benchmark.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2018 Taler Systems SA
+  (C) 2014--2020 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
@@ -18,10 +18,10 @@
 */
 
 /**
- * @file merchant/backend/taler-merchant-httpd.c
- * @brief HTTP serving layer intended to perform crypto-work and
- * communication with the exchange
+ * @file merchant/backend/taler-merchant-benchmark.c
+ * @brief benchmark the backend to evaluate performance
  * @author Marcello Stanisci
+ * @author Christian Grothoff
  */
 #include "platform.h"
 #include <taler/taler_util.h>
@@ -36,60 +36,49 @@
 #include <taler/taler_error_codes.h>
 #include "taler_merchant_testing_lib.h"
 
+/**
+ * Maximum length of an amount (value plus currency string) needed by the test.
+ * We have a 32-bit and a 64-bit value (~48 characters), plus the currency, 
plus
+ * some punctuation.
+ */
+#define MAX_AMOUNT_LEN (TALER_CURRENCY_LEN + 64)
 
-#define APIKEY_SANDBOX "Authorization: ApiKey sandbox"
+/**
+ * Maximum length of an order JSON.  Generously allocated.
+ */
+#define MAX_ORDER_LEN (MAX_AMOUNT_LEN * 4 + 2048)
 
 
 /* Error codes.  */
 enum PaymentGeneratorError
 {
-
-  MISSING_MERCHANT_URL = 2,
-  FAILED_TO_LAUNCH_MERCHANT,
-  MISSING_BANK_URL,
-  FAILED_TO_LAUNCH_BANK,
-  BAD_CLI_ARG,
-  MISSING_CURRENCY
+  PG_SUCCESS = 0,
+  PG_NO_SUBCOMMAND,
+  PG_BAD_OPTIONS,
+  PG_BAD_CONFIG_FILE,
+  PG_FAILED_CFG_CURRENCY,
+  PG_FAILED_TO_PREPARE_MERCHANT,
+  PG_FAILED_TO_PREPARE_BANK,
+  PG_FAILED_TO_LAUNCH_MERCHANT,
+  PG_FAILED_TO_LAUNCH_BANK,
+  PG_RUNTIME_FAILURE
 };
 
-/* Hard-coded params.  Note, the bank is expected to
- * have the Tor user with account number 3 and password 'x'.
- */
-#define EXCHANGE_ACCOUNT_NO 2
-#define USER_LOGIN_NAME "Tor"
-#define USER_LOGIN_PASS "x"
-#define EXCHANGE_URL "http://example.com/";
-#define FIRST_INSTRUCTION -1
-#define TRACKS_INSTRUCTION 9
-#define TWOCOINS_INSTRUCTION 5
 
 /**
- * Help string shown if NO subcommand is given on command line.
+ * What API key should we send in the HTTP 'Authorization' header?
  */
-static int root_help;
+static char *apikey;
 
 /**
  * Witnesses if the ordinary cases payment suite should be run.
  */
-static unsigned int ordinary;
+static bool ordinary;
 
 /**
  * Witnesses if the corner cases payment suite should be run.
  */
-static unsigned int corner;
-
-/**
- * Root help string.
- */
-static const char *root_help_str = \
-  "taler-merchant-benchmark\nPopulates production database"
-  " with fake payments.\nMust be used with either 'ordinary'"
-  " or 'corner' sub-commands.\n";
-
-/**
- * Alternative non default instance.
- */
-static char *alt_instance_id;
+static bool corner;
 
 /**
  * Base URL of the alternative non default instance.
@@ -106,21 +95,6 @@ static unsigned int unaggregated_number = 1;
  */
 static unsigned int twocoins_number = 1;
 
-/**
- * Exit code.
- */
-static int result;
-
-/**
- * Bank process.
- */
-static struct GNUNET_OS_Process *bankd;
-
-/**
- * Merchant process.
- */
-static struct GNUNET_OS_Process *merchantd;
-
 /**
  * How many payments we want to generate.
  */
@@ -131,31 +105,15 @@ static unsigned int payments_number = 1;
  */
 static unsigned int tracks_number = 1;
 
-
 /**
- * Usually set as ~/.config/taler.net
- */
-static const char *default_config_file;
-
-/**
- * Log level used during the run.
- */
-static char *loglev;
-
-/**
- * Config filename.
+ * Config filename to give to commands (like wirewatch).
  */
 static char *cfg_filename;
 
 /**
- * Bank base URL.
+ * Bank configuration.
  */
-static char *bank_url;
-
-/**
- * Log file.
- */
-static char *logfile;
+static struct TALER_TESTING_BankConfiguration bc;
 
 /**
  * Merchant base URL.
@@ -167,145 +125,6 @@ static char *merchant_url;
  */
 static char *currency;
 
-/**
- * Authentication data to use. FIXME: init !
- */
-static struct TALER_BANK_AuthenticationData auth;
-
-static char *exchange_payto;
-static char *customer_payto;
-static char *merchant_payto;
-
-/**
- * Convenience macros to allocate all the currency-dependant
- * strings;  note that the argument list of the macro is ignored.
- * It is kept as a way to make the macro more auto-descriptive
- * where it is called.
- */
-
-// FIXME: This should not be a macro!
-#define ALLOCATE_AMOUNTS(...) \
-  char *CURRENCY_10_02; \
-  char *CURRENCY_10; \
-  char *CURRENCY_9_98; \
-  char *CURRENCY_5_01; \
-  char *CURRENCY_5; \
-  char *CURRENCY_4_99; \
-  char *CURRENCY_0_02; \
-  char *CURRENCY_0_01; \
-  \
-  GNUNET_asprintf (&CURRENCY_10_02, \
-                   "%s:10.02", \
-                   currency); \
-  GNUNET_asprintf (&CURRENCY_10, \
-                   "%s:10", \
-                   currency); \
-  GNUNET_asprintf (&CURRENCY_9_98, \
-                   "%s:9.98", \
-                   currency); \
-  GNUNET_asprintf (&CURRENCY_5_01, \
-                   "%s:5.01", \
-                   currency); \
-  GNUNET_asprintf (&CURRENCY_5, \
-                   "%s:5", \
-                   currency); \
-  GNUNET_asprintf (&CURRENCY_4_99, \
-                   "%s:4.99", \
-                   currency); \
-  GNUNET_asprintf (&CURRENCY_0_02, \
-                   "%s:0.02", \
-                   currency); \
-  GNUNET_asprintf (&CURRENCY_0_01, \
-                   "%s:0.01", \
-                   currency);
-
-// FIXME: this should not be a macro
-// FIXME: the inline JSON is outdated
-// FIXME: find a better way to produce the contract terms via a helper function
-#define ALLOCATE_ORDERS(...) \
-  char *order_worth_5; \
-  char *order_worth_5_track; \
-  char *order_worth_5_unaggregated; \
-  char *order_worth_10_2coins; \
-  \
-  GNUNET_asprintf \
-    (&order_worth_5, \
-    "{\"max_fee\":\
-       {\"currency\":\"%s\",\
-        \"value\":0,\
-        \"fraction\":50000000},\
-       \"refund_deadline\":\"\\/Date(0)\\/\",\
-       \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
-       \"amount\":\
-         {\"currency\":\"%s\",\
-          \"value\":5,\
-          \"fraction\":0},\
-        \"summary\": \"merchant-lib testcase\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice cream\",\
-                         \"value\":\"{%s:5}\"} ] }", \
-    currency, \
-    currency, \
-    currency); \
-  GNUNET_asprintf \
-    (&order_worth_5_track, \
-    "{\"max_fee\":\
-       {\"currency\":\"%s\",\
-        \"value\":0,\
-        \"fraction\":50000000},\
-       \"refund_deadline\":\"\\/Date(0)\\/\",\
-       \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
-       \"amount\":\
-         {\"currency\":\"%s\",\
-          \"value\":5,\
-          \"fraction\":0},\
-        \"summary\": \"ice track cream!\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"ice track cream\",\
-                         \"value\":\"{%s:5}\"} ] }", \
-    currency, \
-    currency, \
-    currency); \
-  GNUNET_asprintf \
-    (&order_worth_5_unaggregated, \
-    "{\"max_fee\":\
-       {\"currency\":\"%s\",\
-        \"value\":0,\
-        \"fraction\":50000000},\
-       \"wire_transfer_delay\":\"\\/Delay(30000)\\/\",\
-       \"refund_deadline\":\"\\/Date(22)\\/\",\
-       \"pay_deadline\":\"\\/Date(1)\\/\",\
-       \"amount\":\
-         {\"currency\":\"%s\",\
-          \"value\":5,\
-          \"fraction\":0},\
-        \"summary\": \"unaggregated deposit!\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"unaggregated cream\",\
-                         \"value\":\"{%s:5}\"} ] }", \
-    currency, \
-    currency, \
-    currency); \
-  GNUNET_asprintf \
-    (&order_worth_10_2coins, \
-    "{\"max_fee\":\
-       {\"currency\":\"%s\",\
-        \"value\":0,\
-        \"fraction\":50000000},\
-       \"refund_deadline\":\"\\/Date(0)\\/\",\
-       \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
-       \"amount\":\
-         {\"currency\":\"%s\",\
-          \"value\":10,\
-          \"fraction\":0},\
-        \"summary\": \"2-coins payment\",\
-        \"fulfillment_url\": \"https://example.com/\",\
-        \"products\": [ {\"description\":\"2-coins payment\",\
-                         \"value\":\"{%s:10}\"} ] }", \
-    currency, \
-    currency, \
-    currency);
-
 
 /**
  * Actual commands collection.
@@ -314,193 +133,256 @@ static void
 run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
-  /* Will be freed by testing-lib.  */
-  GNUNET_assert (GNUNET_OK ==
-                 GNUNET_CURL_append_header (is->ctx,
-                                            APIKEY_SANDBOX));
-  ALLOCATE_AMOUNTS (CURRENCY_10_02,
-                    CURRENCY_9_98,
-                    CURRENCY_5_01,
-                    CURRENCY_5,
-                    CURRENCY_4_99,
-                    CURRENCY_0_02,
-                    CURRENCY_0_01);
-  ALLOCATE_ORDERS (order_worth_5,
-                   order_worth_5_track,
-                   order_worth_5_unaggregated,
-                   order_worth_10_2coins);
-  struct TALER_TESTING_Command ordinary_commands[] = {
-    TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
-                                          CURRENCY_10_02,
-                                          &auth,
-                                          customer_payto),
-    TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
-                                      cfg_filename),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
-                                       "create-reserve-1",
-                                       CURRENCY_5,
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
-                                       "create-reserve-1",
-                                       CURRENCY_5,
-                                       MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-1",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                order_worth_5),
-    TALER_TESTING_cmd_pay ("deposit-simple",
-                           merchant_url,
-                           MHD_HTTP_OK,
-                           "create-proposal-1",
-                           "withdraw-coin-1",
-                           CURRENCY_5,
-                           CURRENCY_4_99,
-                           CURRENCY_0_01),
-    TALER_TESTING_cmd_rewind_ip ("rewind-payments",
-                                 FIRST_INSTRUCTION,
-                                 &payments_number),
-    /* Next proposal-pay cycle will be used by /track CMDs
-     * and so it will not have to be looped over, only /track
-     * CMDs will have to.  */
-    TALER_TESTING_cmd_proposal ("create-proposal-2",
-                                merchant_url,
-                                MHD_HTTP_OK,
-                                order_worth_5_track),
-    TALER_TESTING_cmd_pay ("deposit-simple-2",
-                           merchant_url,
-                           MHD_HTTP_OK,
-                           "create-proposal-2",
-                           "withdraw-coin-2",
-                           CURRENCY_5,
-                           CURRENCY_4_99,
-                           CURRENCY_0_01),
-    /* /track/transaction over deposit-simple-2 */
-
-    TALER_TESTING_cmd_exec_aggregator ("aggregate-1",
-                                       cfg_filename),
-    TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-1",
-                                                  merchant_url,
-                                                  MHD_HTTP_OK,
-                                                  "deposit-simple-2"),
-    TALER_TESTING_cmd_merchant_track_transfer ("track-transfer-1",
-                                               merchant_url,
-                                               MHD_HTTP_OK,
-                                               "track-transaction-1"),
-    TALER_TESTING_cmd_rewind_ip ("rewind-tracks",
-                                 TRACKS_INSTRUCTION,
-                                 &tracks_number),
-    TALER_TESTING_cmd_end ()
-  };
+  char CURRENCY_10_02[MAX_AMOUNT_LEN];
+  char CURRENCY_10[MAX_AMOUNT_LEN];
+  char CURRENCY_9_98[MAX_AMOUNT_LEN];
+  char CURRENCY_5_01[MAX_AMOUNT_LEN];
+  char CURRENCY_5[MAX_AMOUNT_LEN];
+  char CURRENCY_4_99[MAX_AMOUNT_LEN];
+  char CURRENCY_0_02[MAX_AMOUNT_LEN];
+  char CURRENCY_0_01[MAX_AMOUNT_LEN];
+  char order_worth_5[MAX_ORDER_LEN];
+  char order_worth_5_track[MAX_ORDER_LEN];
+  char order_worth_5_unaggregated[MAX_ORDER_LEN];
+  char order_worth_10_2coins[MAX_ORDER_LEN];
+
+  GNUNET_snprintf (CURRENCY_10_02,
+                   sizeof (CURRENCY_10_02),
+                   "%s:10.02",
+                   currency);
+  GNUNET_snprintf (CURRENCY_10,
+                   sizeof (CURRENCY_10),
+                   "%s:10",
+                   currency);
+  GNUNET_snprintf (CURRENCY_9_98,
+                   sizeof (CURRENCY_9_98),
+                   "%s:9.98",
+                   currency);
+  GNUNET_snprintf (CURRENCY_5_01,
+                   sizeof (CURRENCY_5_01),
+                   "%s:5.01",
+                   currency);
+  GNUNET_snprintf (CURRENCY_5,
+                   sizeof (CURRENCY_5),
+                   "%s:5",
+                   currency);
+  GNUNET_snprintf (CURRENCY_4_99,
+                   sizeof (CURRENCY_4_99),
+                   "%s:4.99",
+                   currency);
+  GNUNET_snprintf (CURRENCY_0_02,
+                   sizeof (CURRENCY_0_02),
+                   "%s:0.02",
+                   currency);
+  GNUNET_snprintf (CURRENCY_0_01,
+                   sizeof (CURRENCY_0_01),
+                   "%s:0.01",
+                   currency);
+  GNUNET_snprintf (order_worth_5,
+                   sizeof (order_worth_5),
+                   "{\"max_fee\": {\"currency\":\"%s\", \"value\":0, 
\"fraction\":50000000},"
+                   "\"refund_deadline\":\"\\/Date(0)\\/\","
+                   "\"pay_deadline\":\"\\/Date(99999999999)\\/\","
+                   "\"amount\": {\"currency\":\"%s\", \"value\":5, 
\"fraction\":0},"
+                   "\"summary\": \"merchant-lib testcase\","
+                   "\"fulfillment_url\": \"https://example.com/\",";
+                   "\"products\": [ {\"description\":\"ice cream\", 
\"value\":\"{%s:5}\"} ] }",
+                   currency,
+                   currency,
+                   currency);
+  GNUNET_snprintf (order_worth_5_track,
+                   sizeof (order_worth_5_track),
+                   "{\"max_fee\": {\"currency\":\"%s\", \"value\":0, 
\"fraction\":50000000},"
+                   "\"refund_deadline\":\"\\/Date(0)\\/\","
+                   "\"pay_deadline\":\"\\/Date(99999999999)\\/\","
+                   "\"amount\": {\"currency\":\"%s\", \"value\":5, 
\"fraction\":0},"
+                   "\"summary\": \"ice track cream!\","
+                   "\"fulfillment_url\": \"https://example.com/\",";
+                   "\"products\": [ {\"description\":\"ice track cream\", 
\"value\":\"{%s:5}\"} ] }",
+                   currency,
+                   currency,
+                   currency);
+  GNUNET_snprintf (order_worth_5_unaggregated,
+                   sizeof (order_worth_5_unaggregated),
+                   "{\"max_fee\": {\"currency\":\"%s\", \"value\":0, 
\"fraction\":50000000},"
+                   "\"wire_transfer_delay\":\"\\/Delay(30000)\\/\","
+                   "\"refund_deadline\":\"\\/Date(22)\\/\","
+                   "\"pay_deadline\":\"\\/Date(1)\\/\","
+                   "\"amount\": {\"currency\":\"%s\", \"value\":5, 
\"fraction\":0},"
+                   "\"summary\": \"unaggregated deposit!\","
+                   "\"fulfillment_url\": \"https://example.com/\",";
+                   "\"products\": [ {\"description\":\"unaggregated cream\","
+                   "\"value\":\"{%s:5}\"} ] }",
+                   currency,
+                   currency,
+                   currency);
+  GNUNET_snprintf (order_worth_10_2coins,
+                   sizeof (order_worth_10_2coins),
+                   "{\"max_fee\": {\"currency\":\"%s\", \"value\":0, 
\"fraction\":50000000},"
+                   "\"refund_deadline\":\"\\/Date(0)\\/\","
+                   "\"pay_deadline\":\"\\/Date(99999999999)\\/\","
+                   "\"amount\": {\"currency\":\"%s\",\"value\":10, 
\"fraction\":0},"
+                   "\"summary\": \"2-coins payment\","
+                   "\"fulfillment_url\": \"https://example.com/\",";
+                   "\"products\": [ {\"description\":\"2-coins payment\","
+                   "\"value\":\"{%s:10}\"} ] }",
+                   currency,
+                   currency,
+                   currency);
 
-  struct TALER_TESTING_Command corner_commands[] = {
-
-    TALER_TESTING_cmd_admin_add_incoming
-      ("create-reserve-1",
-      CURRENCY_5_01,
-      &auth,
-      customer_payto),
-
-    TALER_TESTING_cmd_exec_wirewatch
-      ("wirewatch-1",
-      cfg_filename),
-
-    TALER_TESTING_cmd_withdraw_amount
-      ("withdraw-coin-1",
-      "create-reserve-1",
-      CURRENCY_5,
-      MHD_HTTP_OK),
-
-    TALER_TESTING_cmd_proposal
-      ("create-unaggregated-proposal",
-      alt_instance_url,
-      MHD_HTTP_OK,
-      order_worth_5_unaggregated),
-
-    TALER_TESTING_cmd_pay
-      ("deposit-unaggregated",
-      merchant_url,
-      MHD_HTTP_OK,
-      "create-unaggregated-proposal",
-      "withdraw-coin-1",
-      CURRENCY_5,
-      CURRENCY_4_99,
-      CURRENCY_0_01),
-
-    TALER_TESTING_cmd_rewind_ip
-      ("rewind-unaggregated",
-      FIRST_INSTRUCTION,
-      &unaggregated_number),
-
-    TALER_TESTING_cmd_admin_add_incoming
-      ("create-reserve-2",
-      CURRENCY_10_02,
-      &auth,
-      customer_payto),
-
-    TALER_TESTING_cmd_exec_wirewatch
-      ("wirewatch-2",
-      cfg_filename),
-
-    TALER_TESTING_cmd_withdraw_amount
-      ("withdraw-coin-2",
-      "create-reserve-2",
-      CURRENCY_5,
-      MHD_HTTP_OK),
-
-    TALER_TESTING_cmd_withdraw_amount
-      ("withdraw-coin-3",
-      "create-reserve-2",
-      CURRENCY_5,
-      MHD_HTTP_OK),
-
-    TALER_TESTING_cmd_proposal
-      ("create-twocoins-proposal",
-      merchant_url,
-      MHD_HTTP_OK,
-      order_worth_10_2coins),
-
-    TALER_TESTING_cmd_pay
-      ("deposit-twocoins",
-      merchant_url,
-      MHD_HTTP_OK,
-      "create-twocoins-proposal",
-      "withdraw-coin-2;withdraw-coin-3",
-      CURRENCY_10,
-      CURRENCY_9_98,
-      CURRENCY_0_02),
-
-    TALER_TESTING_cmd_exec_aggregator
-      ("aggregate-twocoins",
-      cfg_filename),
-
-    TALER_TESTING_cmd_rewind_ip
-      ("rewind-twocoins",
-      TWOCOINS_INSTRUCTION,
-      &twocoins_number),
-
-    TALER_TESTING_cmd_end ()
-  };
+  if (NULL != apikey)
+  {
+    char *hdr;
+
+    GNUNET_asprintf (&hdr,
+                     "%s: %s",
+                     MHD_HTTP_HEADER_AUTHORIZATION,
+                     apikey);
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_CURL_append_header (is->ctx,
+                                              hdr));
+    GNUNET_free (hdr);
+  }
 
-  if (GNUNET_OK == ordinary)
+  if (ordinary)
   {
+    struct TALER_TESTING_Command ordinary_commands[] = {
+      TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
+                                            CURRENCY_10_02,
+                                            &bc.exchange_auth,
+                                            bc.user43_payto),
+      TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
+                                        cfg_filename),
+      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+                                         "create-reserve-1",
+                                         CURRENCY_5,
+                                         MHD_HTTP_OK),
+      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+                                         "create-reserve-1",
+                                         CURRENCY_5,
+                                         MHD_HTTP_OK),
+      TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1",
+                                              merchant_url,
+                                              MHD_HTTP_OK,
+                                              order_worth_5),
+      TALER_TESTING_cmd_merchant_pay_order ("deposit-simple",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "create-proposal-1",
+                                            "withdraw-coin-1",
+                                            CURRENCY_5,
+                                            CURRENCY_4_99),
+      TALER_TESTING_cmd_rewind_ip ("rewind-payments",
+                                   "create-reserve",
+                                   payments_number),
+      /* Next proposal-pay cycle will be used by /track CMDs
+       * and so it will not have to be looped over, only /track
+       * CMDs will have to.  */
+      TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
+                                              merchant_url,
+                                              MHD_HTTP_OK,
+                                              order_worth_5_track),
+      TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-2",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "create-proposal-2",
+                                            "withdraw-coin-2",
+                                            CURRENCY_5,
+                                            CURRENCY_4_99),
+      /* /track/transaction over deposit-simple-2 */
+
+      TALER_TESTING_cmd_exec_aggregator ("aggregate-1",
+                                         cfg_filename),
+      TALER_TESTING_cmd_merchant_post_transfer (
+        "post-transfer-1",
+        &bc.exchange_auth,
+        bc.exchange_auth.wire_gateway_url,
+        merchant_url,
+        "EUR:4.98",                                         /* FIXME: check 
amount! */
+        MHD_HTTP_OK,
+        "deposit-simple-2",
+        NULL),
+      TALER_TESTING_cmd_merchant_get_transfers ("track-transfer-1",
+                                                merchant_url,
+                                                bc.user42_payto,
+                                                MHD_HTTP_OK,
+                                                "post-transaction-1",
+                                                NULL),
+      TALER_TESTING_cmd_rewind_ip ("rewind-tracks",
+                                   "track-transfer-1",
+                                   tracks_number),
+      TALER_TESTING_cmd_end ()
+    };
+
     TALER_TESTING_run (is,
                        ordinary_commands);
     return;
   }
 
-  if (GNUNET_OK == corner)
+  if (corner) /* should never be 'false' here */
   {
+    struct TALER_TESTING_Command corner_commands[] = {
+      TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1",
+                                            CURRENCY_5_01,
+                                            &bc.exchange_auth,
+                                            bc.user43_payto),
+      TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1",
+                                        cfg_filename),
+      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+                                         "create-reserve-1",
+                                         CURRENCY_5,
+                                         MHD_HTTP_OK),
+      TALER_TESTING_cmd_merchant_post_orders ("create-unaggregated-proposal",
+                                              alt_instance_url,
+                                              MHD_HTTP_OK,
+                                              order_worth_5_unaggregated),
+      TALER_TESTING_cmd_merchant_pay_order ("deposit-unaggregated",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "create-unaggregated-proposal",
+                                            "withdraw-coin-1",
+                                            CURRENCY_5,
+                                            CURRENCY_4_99),
+      TALER_TESTING_cmd_rewind_ip ("rewind-unaggregated",
+                                   "create-reserve-1",
+                                   unaggregated_number),
+      TALER_TESTING_cmd_admin_add_incoming ("create-reserve-2",
+                                            CURRENCY_10_02,
+                                            &bc.exchange_auth,
+                                            bc.user43_payto),
+      TALER_TESTING_cmd_exec_wirewatch ("wirewatch-2",
+                                        cfg_filename),
+      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+                                         "create-reserve-2",
+                                         CURRENCY_5,
+                                         MHD_HTTP_OK),
+      TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-3",
+                                         "create-reserve-2",
+                                         CURRENCY_5,
+                                         MHD_HTTP_OK),
+      TALER_TESTING_cmd_merchant_post_orders ("create-twocoins-proposal",
+                                              merchant_url,
+                                              MHD_HTTP_OK,
+                                              order_worth_10_2coins),
+      TALER_TESTING_cmd_merchant_pay_order ("deposit-twocoins",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "create-twocoins-proposal",
+                                            "withdraw-coin-2;withdraw-coin-3",
+                                            CURRENCY_10,
+                                            CURRENCY_9_98),
+      TALER_TESTING_cmd_exec_aggregator ("aggregate-twocoins",
+                                         cfg_filename),
+      TALER_TESTING_cmd_rewind_ip ("rewind-twocoins",
+                                   "create-reserve-2",
+                                   twocoins_number),
+      TALER_TESTING_cmd_end ()
+    };
+
     TALER_TESTING_run (is,
                        corner_commands);
     return;
   }
-
-  /* Should never get here, as the control on subcommands
-   * happens earlier at launch time.  */
-  fprintf (stderr,
-           "None of 'ordinary' or 'corner'"
-           " subcommands were given\n");
-  result = 1;
 }
 
 
@@ -509,10 +391,11 @@ run (void *cls,
  *
  * @param process process to terminate.
  */
-void
+static void
 terminate_process (struct GNUNET_OS_Process *process)
 {
-  GNUNET_OS_process_kill (process, SIGTERM);
+  GNUNET_OS_process_kill (process,
+                          SIGTERM);
   GNUNET_OS_process_wait (process);
   GNUNET_OS_process_destroy (process);
 }
@@ -529,135 +412,101 @@ int
 main (int argc,
       char *const *argv)
 {
+  char *loglev;
+  char *logfile;
+  char *exchange_account;
+  char *alt_instance_id;
+  struct GNUNET_OS_Process *bankd;
+  struct GNUNET_OS_Process *merchantd;
   struct GNUNET_GETOPT_CommandLineOption *options;
   struct GNUNET_GETOPT_CommandLineOption root_options[] = {
-    GNUNET_GETOPT_option_cfgfile
-      (&cfg_filename),
-    GNUNET_GETOPT_option_version
-      (PACKAGE_VERSION " " VCS_VERSION),
-    GNUNET_GETOPT_option_flag
-      ('h',
-      "help",
-      NULL,
-      &root_help),
+    GNUNET_GETOPT_option_cfgfile (&cfg_filename),
+    GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
+    GNUNET_GETOPT_option_help ("Runs benchmark logic against merchant backend. 
"
+                               "Must be used with either 'ordinary' or 
'corner' sub-commands."),
     GNUNET_GETOPT_OPTION_END
   };
-
   struct GNUNET_GETOPT_CommandLineOption corner_options[] = {
-    GNUNET_GETOPT_option_help
-      ("Populate databases with corner case payments"),
-    GNUNET_GETOPT_option_loglevel
-      (&loglev),
-    GNUNET_GETOPT_option_uint
-      ('u',
-      "unaggregated-number",
-      "UN",
-      "will generate UN unaggregated payments, defaults to 1",
-      &unaggregated_number),
-    GNUNET_GETOPT_option_uint
-      ('t',
-      "two-coins",
-      "TC",
-      "will perform TC 2-coins payments, defaults to 1",
-      &twocoins_number),
-    /**
-     * NOTE: useful when the setup serves merchant
-     * backends via unix domain sockets, since there
-     * is no way - yet? - to get the merchant base url.
-     * Clearly, we could introduce a merchant_base_url
-     * value into the configuration.
-     */GNUNET_GETOPT_option_string
-      ('m',
-      "merchant-url",
-      "MU",
-      "merchant base url, mandatory",
-      &merchant_url),
-    GNUNET_GETOPT_option_string
-      ('k',
-      "currency",
-      "K",
-      "Used currency, mandatory",
-      &currency),
-    GNUNET_GETOPT_option_string
-      ('i',
-      "alt-instance",
-      "AI",
-      "alternative (non default) instance,"
-      " used to provide fresh wire details to"
-      " make unaggregated transactions stay so."
-      " Note, this instance will be given far"
-      " future wire deadline, and so it should"
-      " never author now-deadlined transactions,"
-      " as they would get those far future ones"
-      " aggregated too.",
-      &alt_instance_id),
-    GNUNET_GETOPT_option_string
-      ('b',
-      "bank-url",
-      "BU",
-      "bank base url, mandatory",
-      &bank_url),
-    GNUNET_GETOPT_option_string
-      ('l',
-      "logfile",
-      "LF",
-      "will log to file LF",
-      &logfile),
+    GNUNET_GETOPT_option_help ("Populate databases with corner case payments"),
+    GNUNET_GETOPT_option_loglevel (&loglev),
+    GNUNET_GETOPT_option_uint ('u',
+                               "unaggregated-number",
+                               "UN",
+                               "will generate UN unaggregated payments, 
defaults to 1",
+                               &unaggregated_number),
+    GNUNET_GETOPT_option_uint ('t',
+                               "two-coins",
+                               "TC",
+                               "will perform TC 2-coins payments, defaults to 
1",
+                               &twocoins_number),
+    GNUNET_GETOPT_option_mandatory (
+      GNUNET_GETOPT_option_string ('e',
+                                   "exchange-account",
+                                   "SECTION",
+                                   "configuration section specifying the 
exchange account to use, mandatory",
+                                   &exchange_account)),
+    GNUNET_GETOPT_option_string ('a',
+                                 "apikey",
+                                 "APIKEY",
+                                 "HTTP 'Authorization' header to send to the 
merchant",
+                                 &apikey),
+    GNUNET_GETOPT_option_mandatory (
+      GNUNET_GETOPT_option_string ('i',
+                                   "alt-instance",
+                                   "AI",
+                                   "alternative (non default) instance,"
+                                   " used to provide fresh wire details to"
+                                   " make unaggregated transactions stay so."
+                                   " Note, this instance will be given far"
+                                   " future wire deadline, and so it should"
+                                   " never author now-deadlined transactions,"
+                                   " as they would get those far future ones"
+                                   " aggregated too.",
+                                   &alt_instance_id)),
+    GNUNET_GETOPT_option_string ('l',
+                                 "logfile",
+                                 "LF",
+                                 "will log to file LF",
+                                 &logfile),
     GNUNET_GETOPT_OPTION_END
   };
-
   struct GNUNET_GETOPT_CommandLineOption ordinary_options[] = {
-    GNUNET_GETOPT_option_cfgfile
-      (&cfg_filename),
-    GNUNET_GETOPT_option_version
-      (PACKAGE_VERSION " " VCS_VERSION),
-    GNUNET_GETOPT_option_help
-      ("Generate Taler ordinary payments"
-      " to populate the databases"),
-    GNUNET_GETOPT_option_loglevel
-      (&loglev),
-    GNUNET_GETOPT_option_uint
-      ('p',
-      "payments-number",
-      "PN",
-      "will generate PN payments, defaults to 1",
-      &payments_number),
-    GNUNET_GETOPT_option_uint
-      ('t',
-      "tracks-number",
-      "TN",
-      "will perform TN /track operations, defaults to 1",
-      &tracks_number),
-    /**
-     * NOTE: useful when the setup serves merchant
-     * backends via unix domain sockets, since there
-     * is no way - yet? - to get the merchant base url.
-     * Clearly, we could introduce a merchant_base_url
-     * value into the configuration.
-     */GNUNET_GETOPT_option_string
-      ('m',
-      "merchant-url",
-      "MU",
-      "merchant base url, mandatory",
-      &merchant_url),
-    GNUNET_GETOPT_option_string
-      ('b',
-      "bank-url",
-      "BU",
-      "bank base url, mandatory",
-      &bank_url),
-    GNUNET_GETOPT_option_string
-      ('l',
-      "logfile",
-      "LF",
-      "will log to file LF",
-      &logfile),
+    GNUNET_GETOPT_option_cfgfile (&cfg_filename),
+    GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION),
+    GNUNET_GETOPT_option_help ("Generate Taler ordinary payments"
+                               " to populate the databases"),
+    GNUNET_GETOPT_option_loglevel (&loglev),
+    GNUNET_GETOPT_option_mandatory (
+      GNUNET_GETOPT_option_string ('e',
+                                   "exchange-account",
+                                   "SECTION",
+                                   "configuration section specifying the 
exchange account to use, mandatory",
+                                   &exchange_account)),
+    GNUNET_GETOPT_option_uint ('p',
+                               "payments-number",
+                               "PN",
+                               "will generate PN payments, defaults to 1",
+                               &payments_number),
+    GNUNET_GETOPT_option_string ('a',
+                                 "apikey",
+                                 "APIKEY",
+                                 "HTTP 'Authorization' header to send to the 
merchant",
+                                 &apikey),
+    GNUNET_GETOPT_option_uint ('t',
+                               "tracks-number",
+                               "TN",
+                               "will perform TN /track operations, defaults to 
1",
+                               &tracks_number),
+    GNUNET_GETOPT_option_string ('l',
+                                 "logfile",
+                                 "LF",
+                                 "will log to file LF",
+                                 &logfile),
     GNUNET_GETOPT_OPTION_END
   };
+  const char *default_config_file;
 
-  default_config_file = GNUNET_OS_project_data_get
-                          ()->user_config_file;
-
+  default_config_file = GNUNET_OS_project_data_get ()->user_config_file;
   loglev = NULL;
   GNUNET_log_setup ("taler-merchant-benchmark",
                     loglev,
@@ -667,66 +516,66 @@ main (int argc,
   {
     if (0 == strcmp ("ordinary", argv[1]))
     {
-      ordinary = GNUNET_YES;
+      ordinary = true;
       options = ordinary_options;
     }
     if (0 == strcmp ("corner", argv[1]))
     {
-      corner = GNUNET_YES;
+      corner = true;
       options = corner_options;
     }
   }
 
-  if (GNUNET_SYSERR !=
-      (result = GNUNET_GETOPT_run
-                  ("taler-merchant-benchmark",
-                  options,
-                  argc,
-                  argv)))
   {
-
-    if (GNUNET_YES == root_help)
-    {
-      fprintf (stdout,
-               "%s",
-               root_help_str);
-      return 0;
-    }
-
-    /* --help was given.  */
+    int result;
+
+    result = GNUNET_GETOPT_run ("taler-merchant-benchmark",
+                                options,
+                                argc,
+                                argv);
+    if (GNUNET_SYSERR == result)
+      return PG_BAD_OPTIONS;
     if (0 == result)
-      return 0;
+      return PG_SUCCESS;
   }
-  if (-1 == result)
-    return 1;
-  if ( (GNUNET_YES != ordinary) &&
-       (GNUNET_YES != corner) )
+  if ( (! ordinary) &&
+       (! corner) )
   {
     fprintf (stderr,
              "Please use 'ordinary' or 'corner' subcommands.\n");
-    return 1;
-  }
-
-  if ( (GNUNET_YES == corner) &&
-       (NULL == alt_instance_id) )
-  {
-    fprintf (stderr,
-             "option '-i' is mandatory with sub-command 'corner'!\n");
-    return 1;
+    return PG_NO_SUBCOMMAND;
   }
   if (NULL == cfg_filename)
     cfg_filename = (char *) default_config_file;
-  if (NULL == currency)
+  /* load currency from configuration */
   {
-    TALER_LOG_ERROR ("Option -k is mandatory!\n");
-    return MISSING_CURRENCY;
+    struct GNUNET_CONFIGURATION_Handle *cfg;
+
+    cfg = GNUNET_CONFIGURATION_create ();
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_load (cfg,
+                                   cfg_filename))
+    {
+      TALER_LOG_ERROR ("Could not parse configuration\n");
+      return PG_BAD_CONFIG_FILE;
+    }
+    if (GNUNET_OK !=
+        TALER_config_get_currency (cfg,
+                                   &currency))
+    {
+      TALER_LOG_ERROR ("Failed to read currency from configuration\n");
+      GNUNET_CONFIGURATION_destroy (cfg);
+      return PG_FAILED_CFG_CURRENCY;
+    }
+    GNUNET_CONFIGURATION_destroy (cfg);
   }
+  /* prepare merchant and bank */
+  merchant_url = TALER_TESTING_prepare_merchant (cfg_filename);
   if (NULL == merchant_url)
   {
-    TALER_LOG_ERROR ("Option -m is mandatory!\n");
-    return MISSING_MERCHANT_URL;
+    TALER_LOG_ERROR ("Failed to prepare for the merchant\n");
+    return PG_FAILED_TO_PREPARE_MERCHANT;
   }
-
   if (NULL != alt_instance_id)
   {
     GNUNET_assert (0 < GNUNET_asprintf (&alt_instance_url,
@@ -734,45 +583,39 @@ main (int argc,
                                         merchant_url,
                                         &alt_instance_id));
   }
-
-  if (NULL == (merchantd = TALER_TESTING_run_merchant
-                             (cfg_filename, merchant_url)))
+  if (GNUNET_OK !=
+      TALER_TESTING_prepare_bank (cfg_filename,
+                                  GNUNET_YES,
+                                  exchange_account,
+                                  &bc))
   {
-    TALER_LOG_ERROR ("Failed to launch the merchant\n");
-    return FAILED_TO_LAUNCH_MERCHANT;
+    TALER_LOG_ERROR ("Failed to prepare for the bank\n");
+    return PG_FAILED_TO_PREPARE_BANK;
   }
-
-  if (NULL == bank_url)
+  /* launch merchant and bank */
+  if (NULL == (merchantd = TALER_TESTING_run_merchant (cfg_filename,
+                                                       merchant_url)))
   {
-    TALER_LOG_ERROR ("Option -b is mandatory!\n");
-    terminate_process (merchantd);
-    return MISSING_BANK_URL;
+    TALER_LOG_ERROR ("Failed to launch the merchant\n");
+    return PG_FAILED_TO_LAUNCH_MERCHANT;
   }
-
-  if (NULL == (bankd = TALER_TESTING_run_bank
-                         (cfg_filename,
-                         bank_url)))
+  if (NULL == (bankd = TALER_TESTING_run_bank (cfg_filename,
+                                               
bc.exchange_auth.wire_gateway_url)))
   {
     TALER_LOG_ERROR ("Failed to run the bank\n");
     terminate_process (merchantd);
-    return FAILED_TO_LAUNCH_BANK;
+    return PG_FAILED_TO_LAUNCH_BANK;
   }
 
-  /**
-   * FIXME: Need to retrieve the bank base URL!
-   */
-
-  exchange_payto = "payto://x-taler-bank/localhost/Exchange";
-  merchant_payto = "payto://x-taler-bank/localhost/Merchant";
-  customer_payto = "payto://x-taler-bank/localhost/Customer";
-
-  result = TALER_TESTING_setup_with_exchange
-             (run,
-             NULL,
-             cfg_filename);
-
-  terminate_process (merchantd);
-  terminate_process (bankd);
+  /* launch exchange and run benchmark */
+  {
+    int result;
 
-  return (GNUNET_OK == result) ? 0 : result;
+    result = TALER_TESTING_setup_with_exchange (run,
+                                                NULL,
+                                                cfg_filename);
+    terminate_process (merchantd);
+    terminate_process (bankd);
+    return (GNUNET_OK == result) ? 0 : PG_RUNTIME_FAILURE;
+  }
 }
diff --git a/src/merchant-tools/taler-merchant-setup-reserve.c 
b/src/merchant-tools/taler-merchant-setup-reserve.c
new file mode 100644
index 0000000..5d5f393
--- /dev/null
+++ b/src/merchant-tools/taler-merchant-setup-reserve.c
@@ -0,0 +1,238 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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-setup-reserve.c
+ * @brief Create reserve for tipping
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <microhttpd.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Initial amount the reserve will be filled with.
+ */
+static struct TALER_Amount initial_amount;
+
+/**
+ * Base URL of the merchant (with instance) to create the reserve for.
+ */
+static char *merchant_base_url;
+
+/**
+ * Base URL of the exchange to create the reserve at.
+ */
+static char *exchange_base_url;
+
+/**
+ * Wire method to use.
+ */
+static char *wire_method;
+
+/**
+ * Operation handle.
+ */
+static struct TALER_MERCHANT_PostReservesHandle *prh;
+
+/**
+ * Our context for making HTTP requests.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Reschedule context for #SH_ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+
+/**
+ * Shutdown task (invoked when the process is being terminated)
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+  if (NULL != ctx)
+  {
+    GNUNET_CURL_fini (ctx);
+    ctx = NULL;
+  }
+  if (NULL != rc)
+  {
+    GNUNET_CURL_gnunet_rc_destroy (rc);
+    rc = NULL;
+  }
+  if (NULL != prh)
+  {
+    TALER_MERCHANT_reserves_post_cancel (prh);
+    prh = NULL;
+  }
+}
+
+
+/**
+ * Callbacks of this type are used to work the result of submitting a
+ * POST /reserves request to a merchant
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param reserve_pub public key of the created reserve, NULL on error
+ * @param payto_uri where to make the payment to for filling the reserve, NULL 
on error
+ */
+static void
+result_cb (void *cls,
+           const struct TALER_MERCHANT_HttpResponse *hr,
+           const struct TALER_ReservePublicKeyP *reserve_pub,
+           const char *payto_uri)
+{
+  (void) cls;
+  prh = NULL;
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      char res_str[sizeof (*reserve_pub) * 2 + 1];
+
+      GNUNET_STRINGS_data_to_string (reserve_pub,
+                                     sizeof (*reserve_pub),
+                                     res_str,
+                                     sizeof (res_str));
+      fprintf (stdout,
+               "%s&subject=%s\n",
+               payto_uri,
+               res_str);
+    }
+    break;
+  default:
+    fprintf (stderr,
+             "Unexpected backend failure: %u/%d\n",
+             hr->http_status,
+             (int) hr->ec);
+    global_ret = 1;
+    break;
+  }
+  GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * 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 config configuration
+ */
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *config)
+{
+  /* setup HTTP client event loop */
+  ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+                          &rc);
+  rc = GNUNET_CURL_gnunet_rc_create (ctx);
+  /* setup termination logic */
+  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+                                 NULL);
+  /* run actual (async) operation */
+  prh = TALER_MERCHANT_reserves_post (ctx,
+                                      merchant_base_url,
+                                      &initial_amount,
+                                      exchange_base_url,
+                                      wire_method,
+                                      &result_cb,
+                                      NULL);
+  if (NULL == prh)
+  {
+    fprintf (stderr,
+             "Failed to begin operation!\n");
+    global_ret = 2;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+}
+
+
+/**
+ * The main function for setting up reserves for tipping.
+ *
+ * @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",
+                               "amount to be transferred into the reserve",
+                               &initial_amount)),
+    GNUNET_GETOPT_option_mandatory (
+      GNUNET_GETOPT_option_string ('e',
+                                   "exchange-url",
+                                   "URL",
+                                   "base URL of the exchange to create the 
reserve at",
+                                   &exchange_base_url)),
+    GNUNET_GETOPT_option_mandatory (
+      GNUNET_GETOPT_option_string ('m',
+                                   "merchant-url",
+                                   "URL",
+                                   "base URL of the merchant backend's REST 
API",
+                                   &merchant_base_url)),
+    GNUNET_GETOPT_option_mandatory (
+      GNUNET_GETOPT_option_string ('w',
+                                   "wire-method",
+                                   "METHOD",
+                                   "wire method to use for the wire transfer 
(i.e. IBAN)",
+                                   &wire_method)),
+    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-setup-reserve",
+                                   "INFO",
+                                   NULL));
+  if (GNUNET_OK !=
+      GNUNET_PROGRAM_run (argc, argv,
+                          "taler-merchant-setup-reserve",
+                          "Setup reserve for tipping",
+                          options,
+                          &run, NULL))
+    return 3;
+  return global_ret;
+}
+
+
+/* end of taler-merchant-setup-reserve.c */
diff --git a/src/lib/Makefile.am b/src/testing/Makefile.am
similarity index 58%
copy from src/lib/Makefile.am
copy to src/testing/Makefile.am
index 1561f57..8ba034b 100644
--- a/src/lib/Makefile.am
+++ b/src/testing/Makefile.am
@@ -7,64 +7,48 @@ if USE_COVERAGE
 endif
 
 lib_LTLIBRARIES = \
-  libtalermerchant.la \
   libtalermerchanttesting.la
 
-libtalermerchant_la_LDFLAGS = \
-  -version-info 2:0:0 \
-  -no-undefined
-
 libtalermerchanttesting_la_LDFLAGS = \
   -version-info 2:0:0 \
   -no-undefined
 
-libtalermerchant_la_SOURCES = \
-  merchant_api_check_payment.c \
-  merchant_api_common.c \
-  merchant_api_config.c \
-  merchant_api_history.c \
-  merchant_api_proposal.c \
-  merchant_api_proposal_lookup.c \
-  merchant_api_pay.c \
-  merchant_api_poll_payment.c \
-  merchant_api_refund.c \
-  merchant_api_refund_increase.c \
-  merchant_api_tip_authorize.c \
-  merchant_api_tip_pickup.c \
-  merchant_api_tip_pickup2.c \
-  merchant_api_tip_query.c \
-  merchant_api_track_transaction.c \
-  merchant_api_track_transfer.c
-libtalermerchant_la_LIBADD = \
-  -ltalerexchange \
-  -ltalercurl \
-  -ltalerjson \
-  -ltalerutil \
-  -lgnunetcurl \
-  -lgnunetjson \
-  -lgnunetutil \
-  -ljansson \
-  $(XLIB)
-
 libtalermerchanttesting_la_SOURCES = \
-  testing_api_cmd_check_payment.c \
   testing_api_cmd_config.c \
-  testing_api_cmd_history.c \
-  testing_api_cmd_pay.c \
-  testing_api_cmd_pay_abort.c \
-  testing_api_cmd_pay_abort_refund.c \
-  testing_api_cmd_poll_payment.c \
-  testing_api_cmd_proposal.c \
-  testing_api_cmd_proposal_lookup.c \
-  testing_api_cmd_refund_increase.c \
-  testing_api_cmd_refund_lookup.c \
-  testing_api_cmd_rewind.c \
+  testing_api_cmd_abort_order.c \
+  testing_api_cmd_claim_order.c \
+  testing_api_cmd_get_instance.c \
+  testing_api_cmd_get_instances.c \
+  testing_api_cmd_get_orders.c \
+  testing_api_cmd_get_product.c \
+  testing_api_cmd_get_products.c \
+  testing_api_cmd_get_reserve.c \
+  testing_api_cmd_get_reserves.c \
+  testing_api_cmd_get_tips.c \
+  testing_api_cmd_get_transfers.c \
+  testing_api_cmd_delete_instance.c \
+  testing_api_cmd_delete_order.c \
+  testing_api_cmd_delete_product.c \
+  testing_api_cmd_delete_reserve.c \
+  testing_api_cmd_lock_product.c \
+  testing_api_cmd_merchant_get_order.c \
+  testing_api_cmd_merchant_get_tip.c \
+  testing_api_cmd_pay_order.c \
+  testing_api_cmd_post_instances.c \
+  testing_api_cmd_post_orders.c \
+  testing_api_cmd_post_products.c \
+  testing_api_cmd_post_reserves.c \
+  testing_api_cmd_post_transfers.c \
+  testing_api_cmd_patch_instance.c \
+  testing_api_cmd_patch_product.c \
+  testing_api_cmd_refund_order.c \
+   \
   testing_api_cmd_tip_authorize.c \
   testing_api_cmd_tip_pickup.c \
-  testing_api_cmd_tip_query.c \
-  testing_api_cmd_track_transaction.c \
-  testing_api_cmd_track_transfer.c \
+  testing_api_cmd_wallet_get_order.c \
+  testing_api_cmd_wallet_get_tip.c \
   testing_api_helpers.c \
+  testing_api_trait_claim_nonce.c \
   testing_api_trait_merchant_sig.c \
   testing_api_trait_string.c \
   testing_api_trait_hash.c \
@@ -72,7 +56,7 @@ libtalermerchanttesting_la_SOURCES = \
   testing_api_trait_refund_entry.c
 
 libtalermerchanttesting_la_LIBADD = \
-  libtalermerchant.la \
+  $(top_srcdir)/src/lib/libtalermerchant.la \
   -ltalerexchange \
   -ltalerjson \
   -ltalerutil \
@@ -83,13 +67,6 @@ libtalermerchanttesting_la_LIBADD = \
   -ltalertesting \
   $(XLIB)
 
-if HAVE_LIBCURL
-libtalermerchant_la_LIBADD += -lcurl
-else
-if HAVE_LIBGNURL
-libtalermerchant_la_LIBADD += -lgnurl
-endif
-endif
 
 if HAVE_TALERFAKEBANK
 check_PROGRAMS = \
@@ -98,7 +75,6 @@ check_PROGRAMS = \
 if HAVE_TWISTER
 check_PROGRAMS += test_merchant_api_twisted
 endif
-
 endif
 
 TESTS = \
@@ -108,7 +84,7 @@ test_merchant_api_twisted_SOURCES = \
   test_merchant_api_twisted.c
 test_merchant_api_twisted_LDADD = \
   $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
-  libtalermerchant.la \
+  $(top_srcdir)/src/lib/libtalermerchant.la \
   $(LIBGCRYPT_LIBS) \
   -ltalertesting \
   -ltalermerchanttesting \
@@ -127,11 +103,11 @@ test_merchant_api_twisted_LDADD = \
 test_merchant_api_SOURCES = \
   test_merchant_api.c
 test_merchant_api_LDADD = \
+  libtalermerchanttesting.la \
   $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
-  libtalermerchant.la \
+  $(top_srcdir)/src/lib/libtalermerchant.la \
   $(LIBGCRYPT_LIBS) \
   -ltalertesting \
-  -ltalermerchanttesting \
   -ltalerfakebank \
   -ltalerbank \
   -ltalerexchange \
diff --git a/src/lib/reserve_dtip.priv b/src/testing/reserve_dtip.priv
similarity index 100%
rename from src/lib/reserve_dtip.priv
rename to src/testing/reserve_dtip.priv
diff --git a/src/lib/reserve_tip.priv b/src/testing/reserve_tip.priv
similarity index 100%
rename from src/lib/reserve_tip.priv
rename to src/testing/reserve_tip.priv
diff --git a/src/lib/test_merchant.priv b/src/testing/test_merchant.priv
similarity index 100%
rename from src/lib/test_merchant.priv
rename to src/testing/test_merchant.priv
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
new file mode 100644
index 0000000..02a70f3
--- /dev/null
+++ b/src/testing/test_merchant_api.c
@@ -0,0 +1,1192 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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 lib/test_merchant_api.c
+ * @brief testcase to test exchange's HTTP API interface
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include <taler/taler_bank_service.h>
+#include <taler/taler_fakebank_lib.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_error_codes.h>
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * Configuration file we use.  One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_merchant_api.conf"
+
+#define PAYTO_I1 "payto://x-taler-bank/localhost/3"
+
+/**
+ * Exchange base URL.  Could also be taken from config.
+ */
+#define EXCHANGE_URL "http://localhost:8081/";
+
+static const char *pickup_amounts_1[] = {"EUR:5", NULL};
+
+static const char *pickup_amounts_2[] = {"EUR:0.01", NULL};
+
+/**
+ * Payto URI of the customer (payer).
+ */
+static char *payer_payto;
+
+/**
+ * Payto URI of the exchange (escrow account).
+ */
+static char *exchange_payto;
+
+/**
+ * Payto URI of the merchant (receiver).
+ */
+static char *merchant_payto;
+
+/**
+ * Configuration of the bank.
+ */
+static struct TALER_TESTING_BankConfiguration bc;
+
+/**
+ * Configuration of the exchange.
+ */
+static struct TALER_TESTING_ExchangeConfiguration ec;
+
+/**
+ * Merchant base URL.
+ */
+static char *merchant_url;
+
+/**
+ * Merchant process.
+ */
+static struct GNUNET_OS_Process *merchantd;
+
+/**
+ * Map for #intern()
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *interned_strings;
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NAME "2"
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NAME "62"
+
+/**
+ * Account number of some other user.
+ */
+#define USER_ACCOUNT_NAME2 "63"
+
+/**
+ * Account number used by the merchant
+ */
+#define MERCHANT_ACCOUNT_NAME "3"
+
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+static struct TALER_TESTING_Command
+cmd_exec_wirewatch (char *label)
+{
+  return TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE);
+}
+
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+  TALER_TESTING_cmd_exec_aggregator (label "-aggregator", CONFIG_FILE), \
+  TALER_TESTING_cmd_exec_transfer (label "-transfer", CONFIG_FILE)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ * @param url exchange_url
+ */
+static struct TALER_TESTING_Command
+cmd_transfer_to_exchange (const char *label,
+                          const char *amount)
+{
+  return TALER_TESTING_cmd_admin_add_incoming (label,
+                                               amount,
+                                               &bc.exchange_auth,
+                                               payer_payto);
+}
+
+
+static const char *
+intern (const char *str)
+{
+  struct GNUNET_HashCode hash;
+  const char *hs;
+
+  if (NULL == interned_strings)
+    interned_strings = GNUNET_CONTAINER_multihashmap_create (32, GNUNET_NO);
+  GNUNET_assert (NULL != interned_strings);
+  GNUNET_CRYPTO_hash (str, strlen (str), &hash);
+  hs = GNUNET_CONTAINER_multihashmap_get (interned_strings, &hash);
+  if (NULL != hs)
+    return hs;
+  hs = GNUNET_strdup (str);
+  GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (
+                   interned_strings,
+                   &hash,
+                   (void *) hs,
+                   GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  return hs;
+}
+
+
+#define BUF_SZ 512
+
+static const char *
+merchant_url_internal (const char *instance_id)
+{
+  char buf[BUF_SZ];
+
+  if (NULL == instance_id)
+    GNUNET_snprintf (buf,
+                     BUF_SZ,
+                     "%s",
+                     merchant_url);
+  else
+    GNUNET_snprintf (buf,
+                     BUF_SZ,
+                     "%sinstances/%s/",
+                     merchant_url,
+                     instance_id);
+  return intern (buf);
+}
+
+
+static const char *
+merchant_url_external (const char *instance_id)
+{
+  char buf[BUF_SZ];
+  if (NULL == instance_id)
+    GNUNET_snprintf (buf,
+                     BUF_SZ,
+                     "%spublic/",
+                     merchant_url);
+  else
+    GNUNET_snprintf (buf,
+                     BUF_SZ,
+                     "%spublic/instances/%s/",
+                     merchant_url,
+                     instance_id);
+  return intern (buf);
+}
+
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+     struct TALER_TESTING_Interpreter *is)
+{
+  struct TALER_TESTING_Command pay[] = {
+    /**
+     * Move money to the exchange's bank account.
+     */
+    cmd_transfer_to_exchange ("create-reserve-1",
+                              "EUR:10.02"),
+    /**
+     * Make a reserve exist,
+     * according to the previous
+     * transfer.
+     *///
+    cmd_exec_wirewatch ("wirewatch-1"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
+                                                 "EUR:10.02",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-1"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+                                       "create-reserve-1",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+                                       "create-reserve-1",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_get_orders ("get-orders-empty",
+                                           merchant_url,
+                                           MHD_HTTP_OK,
+                                           NULL),
+    /**
+     * Check the reserve is depleted.
+     */
+    TALER_TESTING_cmd_status ("withdraw-status-1",
+                              "create-reserve-1",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_poll_orders_start ("poll-orders-1-start",
+                                         merchant_url,
+                                         GNUNET_TIME_UNIT_MINUTES),
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"1\",\
+        \"refund_deadline\": {\"t_ms\": 0},\
+        \"pay_deadline\": {\"t_ms\": \"never\" },\
+        \"amount\":\"EUR:5.0\",\
+        \"summary\": \"merchant-lib testcase\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }"),
+    TALER_TESTING_cmd_poll_orders_conclude ("poll-orders-1-conclude",
+                                            MHD_HTTP_OK,
+                                            "poll-orders-1-start"),
+    TALER_TESTING_cmd_merchant_get_orders ("get-orders-1",
+                                           merchant_url,
+                                           MHD_HTTP_OK,
+                                           "create-proposal-1",
+                                           NULL),
+    TALER_TESTING_cmd_wallet_get_order ("get-order-wallet-1",
+                                        merchant_url,
+                                        "create-proposal-1",
+                                        false,
+                                        false,
+                                        MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_get_order ("get-order-merchant-1",
+                                          merchant_url,
+                                          "create-proposal-1",
+                                          false,
+                                          false,
+                                          MHD_HTTP_OK),
+#if 0
+    TALER_TESTING_cmd_check_payment ("check-payment-1",
+                                     merchant_url,
+                                     MHD_HTTP_OK,
+                                     "create-proposal-1",
+                                     GNUNET_NO),
+    TALER_TESTING_cmd_poll_payment_start ("poll-payment-1",
+                                          merchant_url,
+                                          "create-proposal-1",
+                                          NULL,
+                                          GNUNET_TIME_UNIT_MILLISECONDS),
+    TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-conclude-1",
+                                             MHD_HTTP_OK,
+                                             "poll-payment-1",
+                                             GNUNET_NO),
+    TALER_TESTING_cmd_poll_payment_start ("poll-payment-2",
+                                          merchant_url,
+                                          "create-proposal-1",
+                                          NULL,
+                                          GNUNET_TIME_UNIT_MINUTES),
+    TALER_TESTING_cmd_check_payment_start ("check-payment-2",
+                                           merchant_url,
+                                           "create-proposal-1",
+                                           GNUNET_TIME_UNIT_MINUTES),
+#endif
+    TALER_TESTING_cmd_poll_order_start ("poll-order-merchant-1-start",
+                                        merchant_url,
+                                        "1",
+                                        GNUNET_TIME_UNIT_MINUTES),
+    TALER_TESTING_cmd_merchant_pay_order ("deposit-simple",
+                                          merchant_url,
+                                          MHD_HTTP_OK,
+                                          "create-proposal-1",
+                                          "withdraw-coin-1",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+    TALER_TESTING_cmd_poll_order_conclude ("poll-order-merchant-1-conclude",
+                                           MHD_HTTP_OK,
+                                           "poll-order-merchant-1-start"),
+    TALER_TESTING_cmd_wallet_get_order ("get-order-wallet-1-2",
+                                        merchant_url,
+                                        "create-proposal-1",
+                                        true,
+                                        false,
+                                        MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_get_order ("get-order-merchant-1-2",
+                                          merchant_url,
+                                          "create-proposal-1",
+                                          true,
+                                          false,
+                                          MHD_HTTP_OK),
+#if 0
+    TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-conclude-2",
+                                             MHD_HTTP_OK,
+                                             "poll-payment-2",
+                                             GNUNET_YES),
+    TALER_TESTING_cmd_check_payment_conclude ("check-payment-conclude-2",
+                                              MHD_HTTP_OK,
+                                              "check-payment-2",
+                                              GNUNET_YES),
+    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-2",
+                                            merchant_url,
+                                            "deposit-simple",
+                                            MHD_HTTP_FORBIDDEN),
+#endif
+    TALER_TESTING_cmd_merchant_pay_order ("replay-simple",
+                                          merchant_url,
+                                          MHD_HTTP_OK,
+                                          "create-proposal-1",
+                                          "withdraw-coin-1",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-1"),
+    CMD_EXEC_AGGREGATOR ("run-aggregator"),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-498c",
+                                           EXCHANGE_URL,
+                                           "EUR:4.98",
+                                           exchange_payto,
+                                           merchant_payto),
+    TALER_TESTING_cmd_merchant_post_transfer ("post-transfer-1",
+                                              &bc.exchange_auth,
+                                              PAYTO_I1,
+                                              merchant_url,
+                                              "EUR:4.98",
+                                              MHD_HTTP_OK,
+                                              "deposit-simple",
+                                              NULL),
+    TALER_TESTING_cmd_merchant_get_transfers ("get-transfers-1",
+                                              merchant_url,
+                                              PAYTO_I1,
+                                              MHD_HTTP_OK,
+                                              "post-transfer-1",
+                                              NULL),
+    TALER_TESTING_cmd_merchant_delete_order ("delete-order-1",
+                                             merchant_url,
+                                             "1",
+                                             MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"),
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command double_spending[] = {
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"2\",\
+        \"refund_deadline\": {\"t_ms\": 0},\
+        \"pay_deadline\": {\"t_ms\": \"never\" },\
+        \"amount\":\"EUR:5.0\",\
+        \"summary\": \"useful product\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }"),
+    TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal-2",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "create-proposal-2",
+                                            NULL),
+    TALER_TESTING_cmd_merchant_pay_order ("deposit-double-2",
+                                          merchant_url,
+                                          MHD_HTTP_CONFLICT,
+                                          "create-proposal-2",
+                                          "withdraw-coin-1",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+#if 0
+    TALER_TESTING_cmd_history ("history-0",
+                               merchant_url,
+                               MHD_HTTP_OK,
+                               /**
+                                * all records to be returned; setting date as 
0 lets the
+                                * interpreter set it as 'now' + one hour 
delta, just to
+                                * make sure it surpasses the proposal's 
timestamp.
+                                */GNUNET_TIME_UNIT_ZERO_ABS,
+                               /**
+                                * We only expect ONE result 
(create-proposal-1) to be
+                                * included in /history response, because 
create-proposal-3
+                                * did NOT go through because of double 
spending.
+                                */1, // nresult
+                               10, // start
+                               -10), // nrows
+#endif
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command refund[] = {
+    cmd_transfer_to_exchange ("create-reserve-1r",
+                              "EUR:10.02"),
+    /**
+     * Make a reserve exist, according to the previous transfer.
+     *///
+    cmd_exec_wirewatch ("wirewatch-1r"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2r",
+                                                 "EUR:10.02",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-1r"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1r",
+                                       "create-reserve-1r",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2r",
+                                       "create-reserve-1r",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    /**
+     * Check the reserve is depleted.
+     */
+    TALER_TESTING_cmd_status ("withdraw-status-1r",
+                              "create-reserve-1r",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1r",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"1r\",\
+        \"refund_deadline\": {\"t_ms\": 0},\
+        \"pay_deadline\": {\"t_ms\": \"never\" },\
+        \"amount\":\"EUR:5.0\",\
+        \"summary\": \"merchant-lib testcase\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }"),
+    TALER_TESTING_cmd_merchant_pay_order ("pay-for-refund-1r",
+                                          merchant_url,
+                                          MHD_HTTP_OK,
+                                          "create-proposal-1r",
+                                          "withdraw-coin-1r",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+#if 0
+    TALER_TESTING_cmd_poll_payment_start ("poll-payment-refund-1",
+                                          merchant_url,
+                                          "create-proposal-1r",
+                                          "EUR:0.0",
+                                          GNUNET_TIME_UNIT_MINUTES),
+#endif
+    TALER_TESTING_cmd_merchant_order_refund ("refund-increase-1r",
+                                             merchant_url,
+                                             "refund test",
+                                             "1r", /* order ID */
+                                             "EUR:0.1",
+                                             MHD_HTTP_OK),
+    TALER_TESTING_cmd_wallet_get_order ("get-order-wallet-1r",
+                                        merchant_url,
+                                        "create-proposal-1r",
+                                        true,
+                                        true,
+                                        MHD_HTTP_OK,
+                                        "refund-increase-1r",
+                                        NULL),
+    TALER_TESTING_cmd_merchant_get_order ("get-order-merchant-1r",
+                                          merchant_url,
+                                          "create-proposal-1r",
+                                          true,
+                                          true,
+                                          MHD_HTTP_OK,
+                                          "refund-increase-1r",
+                                          NULL),
+#if 0
+    TALER_TESTING_cmd_poll_payment_conclude ("poll-payment-refund-conclude-1",
+                                             MHD_HTTP_OK,
+                                             "poll-payment-refund-1",
+                                             GNUNET_YES),
+    /* Ordinary refund.  */
+    TALER_TESTING_cmd_refund_lookup ("refund-lookup-1r",
+                                     merchant_url,
+                                     "refund-increase-1r",
+                                     "pay-for-refund-1r",
+                                     "1r",
+                                     MHD_HTTP_OK),
+    /* Trying to pick up refund from non existent proposal.  */
+    TALER_TESTING_cmd_refund_lookup ("refund-lookup-non-existent",
+                                     merchant_url,
+                                     "refund-increase-1r",
+                                     "deposit-simple",
+                                     "non-existend-id",
+                                     MHD_HTTP_NOT_FOUND),
+#endif
+
+    /* Test /refund on a contract that was never paid.  */
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-not-to-be-paid",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"1-unpaid\",\
+        \"refund_deadline\":{\"t_ms\":0},\
+        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"amount\":\"EUR:5.0\",\
+        \"summary\": \"useful product\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }"),
+    /* Try to increase a non paid proposal.  */
+    TALER_TESTING_cmd_merchant_order_refund ("refund-increase-unpaid-proposal",
+                                             merchant_url,
+                                             "refund test",
+                                             "1-unpaid",
+                                             "EUR:0.1",
+                                             MHD_HTTP_CONFLICT),
+    /* Try to increase a non existent proposal.  */
+    TALER_TESTING_cmd_merchant_order_refund (
+      "refund-increase-nonexistent-proposal",
+      merchant_url,
+      "refund test",
+      "non-existent-id",
+      "EUR:0.1",
+      MHD_HTTP_NOT_FOUND),
+    /*
+       The following block will (1) create a new
+       reserve, then (2) a proposal, then (3) pay for
+       it, and finally (4) attempt to pick up a refund
+       from it without any increasing taking place
+       in the first place.
+    *///
+    cmd_transfer_to_exchange ("create-reserve-unincreased-refund",
+                              "EUR:5.01"),
+    cmd_exec_wirewatch ("wirewatch-unincreased-refund"),
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "check_bank_transfer-unincreased-refund",
+      "EUR:5.01",
+      payer_payto,
+      exchange_payto,
+      "create-reserve-unincreased-refund"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unincreased-refund",
+                                       "create-reserve-unincreased-refund",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_post_orders (
+      "create-proposal-unincreased-refund",
+      merchant_url,
+      MHD_HTTP_OK,
+      "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"unincreased-proposal\",\
+        \"refund_deadline\":{\"t_ms\":0},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
+        \"amount\":\"EUR:5.0\",\
+        \"summary\": \"merchant-lib testcase\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }"),
+    TALER_TESTING_cmd_merchant_pay_order ("pay-unincreased-proposal",
+                                          merchant_url,
+                                          MHD_HTTP_OK,
+                                          "create-proposal-unincreased-refund",
+                                          "withdraw-coin-unincreased-refund",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+    CMD_EXEC_AGGREGATOR ("run-aggregator-unincreased-refund"),
+    TALER_TESTING_cmd_check_bank_transfer (
+      "check_bank_transfer-paid-unincreased-refund",
+      EXCHANGE_URL,
+      "EUR:9.88", /* '4.98 from above', plus 4.99 from 'pay-for-refund-1r'
+                     and MINUS 0.1 PLUS 0.01 (deposit fee) from 
'refund-increase-1r' */
+      exchange_payto,
+      merchant_payto),
+    /* Actually try to pick up the refund from the "unincreased proposal".  */
+#if 0
+    TALER_TESTING_cmd_refund_lookup_with_amount ("refund-lookup-unincreased",
+                                                 merchant_url,
+                                                 NULL,
+                                                 "pay-unincreased-proposal",
+                                                 "unincreased-proposal",
+                                                 MHD_HTTP_NOT_FOUND,
+                                                 /* If a lookup is attempted
+                                                  * on an unincreased
+                                                  * proposal, the backend will
+                                                  * simply respond with a
+                                                  * empty refunded coin "set",
+                                                  * but the HTTP response code
+                                                  * is 200 OK.  *///
+                                                 "EUR:0"),
+#endif
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command tip[] = {
+    TALER_TESTING_cmd_merchant_post_reserves ("create-reserve-tip-1",
+                                              merchant_url,
+                                              "EUR:20.04",
+                                              EXCHANGE_URL,
+                                              "x-taler-bank",
+                                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-tip-1-exch",
+                                                   "EUR:20.04",
+                                                   &bc.exchange_auth,
+                                                   payer_payto,
+                                                   "create-reserve-tip-1"),
+    cmd_exec_wirewatch ("wirewatch-3"),
+    /* Sleep so the merchant discovers that the reserve exists at the exchange
+       before trying to authorize a tip. */
+    TALER_TESTING_cmd_sleep ("wait-reserve-1",
+                             1),
+    TALER_TESTING_cmd_tip_authorize ("authorize-tip-1",
+                                     merchant_url,
+                                     EXCHANGE_URL,
+                                     MHD_HTTP_OK,
+                                     "tip 1",
+                                     "EUR:5.01"),
+    TALER_TESTING_cmd_tip_authorize_from_reserve ("authorize-tip-2",
+                                                  merchant_url,
+                                                  EXCHANGE_URL,
+                                                  "create-reserve-tip-1",
+                                                  MHD_HTTP_OK,
+                                                  "tip 2",
+                                                  "EUR:5.01"),
+    TALER_TESTING_cmd_wallet_get_tip ("get-tip-1",
+                                      merchant_url,
+                                      "authorize-tip-1",
+                                      MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_get_tip ("merchant-get-tip-1",
+                                        merchant_url,
+                                        "authorize-tip-1",
+                                        MHD_HTTP_OK),
+    TALER_TESTING_cmd_get_tips ("get-tips-1",
+                                merchant_url,
+                                MHD_HTTP_OK,
+                                "authorize-tip-2",
+                                "authorize-tip-1",
+                                NULL),
+    TALER_TESTING_cmd_merchant_get_reserves ("get-reserves-1",
+                                             merchant_url,
+                                             MHD_HTTP_OK,
+                                             "create-reserve-tip-1",
+                                             NULL),
+    TALER_TESTING_cmd_merchant_get_reserve ("get-reserve-1",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "create-reserve-tip-1"),
+    TALER_TESTING_cmd_merchant_get_reserve_with_tips ("get-reserve-2",
+                                                      merchant_url,
+                                                      MHD_HTTP_OK,
+                                                      "create-reserve-tip-1",
+                                                      "authorize-tip-1",
+                                                      "authorize-tip-2",
+                                                      NULL),
+    TALER_TESTING_cmd_tip_pickup ("pickup-tip-1",
+                                  merchant_url,
+                                  MHD_HTTP_OK,
+                                  "authorize-tip-1",
+                                  pickup_amounts_1),
+    TALER_TESTING_cmd_wallet_get_tip2 ("query-tip-2",
+                                       merchant_url,
+                                       "authorize-tip-1",
+                                       "EUR:0.01",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_tip_pickup ("pickup-tip-2",
+                                  merchant_url,
+                                  MHD_HTTP_OK,
+                                  "authorize-tip-2",
+                                  pickup_amounts_1),
+
+    TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-tip-3-too-much",
+                                          merchant_url,
+                                          MHD_HTTP_BAD_REQUEST,
+                                          "authorize-tip-1",
+                                          pickup_amounts_1,
+                                          
TALER_EC_TIP_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING),
+
+    TALER_TESTING_cmd_tip_pickup ("pickup-tip-4",
+                                  merchant_url,
+                                  MHD_HTTP_OK,
+                                  "authorize-tip-1",
+                                  pickup_amounts_2),
+    TALER_TESTING_cmd_merchant_get_tip_with_pickups ("merchant-get-tip-2",
+                                                     merchant_url,
+                                                     "authorize-tip-1",
+                                                     MHD_HTTP_OK,
+                                                     "pickup-tip-1",
+                                                     "pickup-tip-4",
+                                                     NULL),
+
+    /* This command tests the authorization of tip
+     * against a reserve that does not exist.  This is
+     * implemented by passing a "tip instance" that
+     * specifies a reserve key that was never used to
+     * actually create a reserve.  *///
+    TALER_TESTING_cmd_merchant_post_reserves_fake 
("create-reserve-tip-2-fake"),
+    TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec ("authorize-tip-null",
+                                                          merchant_url,
+                                                          EXCHANGE_URL,
+                                                          
"create-reserve-tip-2-fake",
+                                                          MHD_HTTP_NOT_FOUND,
+                                                          "tip 3",
+                                                          "EUR:5.01",
+                                                          
TALER_EC_TIP_AUTHORIZE_DB_RESERVE_NOT_FOUND),
+
+    // Test reserve with insufficient funds
+    TALER_TESTING_cmd_merchant_post_reserves ("create-reserve-tip-2",
+                                              merchant_url,
+                                              "EUR:1.04",
+                                              EXCHANGE_URL,
+                                              "x-taler-bank",
+                                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_admin_add_incoming_with_ref ("create-reserve-tip-2-exch",
+                                                   "EUR:1.04",
+                                                   &bc.exchange_auth,
+                                                   payer_payto,
+                                                   "create-reserve-tip-2"),
+    cmd_exec_wirewatch ("wirewatch-4"),
+    TALER_TESTING_cmd_sleep ("wait-reserve-2",
+                             1),
+    TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec (
+      "authorize-tip-insufficient-funds",
+      merchant_url,
+      EXCHANGE_URL,
+      "create-reserve-tip-2",
+      MHD_HTTP_PRECONDITION_FAILED,
+      "tip 4",
+      "EUR:5.01",
+      TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS),
+
+    TALER_TESTING_cmd_tip_authorize_fake ("fake-tip-authorization"),
+    TALER_TESTING_cmd_tip_pickup_with_ec ("pickup-non-existent-id",
+                                          merchant_url,
+                                          MHD_HTTP_NOT_FOUND,
+                                          "fake-tip-authorization",
+                                          pickup_amounts_1,
+                                          TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN),
+
+    TALER_TESTING_cmd_merchant_get_reserves ("get-reserves-2",
+                                             merchant_url,
+                                             MHD_HTTP_OK,
+                                             "create-reserve-tip-1",
+                                             "create-reserve-tip-2",
+                                             NULL),
+
+    TALER_TESTING_cmd_merchant_delete_reserve ("delete-reserve-tip-1",
+                                               merchant_url,
+                                               "create-reserve-tip-1",
+                                               MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_purge_reserve ("delete-reserve-tip-2",
+                                              merchant_url,
+                                              "create-reserve-tip-1",
+                                              MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_purge_reserve ("delete-reserve-tip-3",
+                                              merchant_url,
+                                              "create-reserve-tip-1",
+                                              MHD_HTTP_NOT_FOUND),
+#if 0
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-tip-1",
+                                            merchant_url_internal ("tip"),
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"1-tip\",                           \
+        \"refund_deadline\":{\"t_ms\":0},\
+        \"pay_deadline\":{\"t_ms\":99999999999999},\
+        \"amount\":\"EUR:5.0\",\
+        \"summary\": \"useful product\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }"),
+    TALER_TESTING_cmd_merchant_pay_order ("deposit-tip-simple",
+                                          merchant_url_external ("tip"),
+                                          MHD_HTTP_OK,
+                                          "create-proposal-tip-1",
+                                          "pickup-tip-1",
+                                          "EUR:5", // amount + fee
+                                          "EUR:4.99"), // amount - fee
+    CMD_EXEC_AGGREGATOR ("aggregator-tip-1"),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-tip-498c",
+                                           EXCHANGE_URL,
+                                           "EUR:4.98",
+                                           exchange_payto,
+                                           merchant_payto),
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-at-tips"),
+#endif
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command pay_again[] = {
+    cmd_transfer_to_exchange ("create-reserve-10",
+                              "EUR:10.02"),
+    cmd_exec_wirewatch ("wirewatch-10"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-10",
+                                                 "EUR:10.02",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-10"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10a",
+                                       "create-reserve-10",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10b",
+                                       "create-reserve-10",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_status ("withdraw-status-10",
+                              "create-reserve-10",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-10",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"10\",\
+        \"refund_deadline\":{\"t_ms\":0},\
+        \"pay_deadline\":{\"t_ms\":99999999999999},\
+        \"amount\":\"EUR:10.0\",\
+        \"summary\": \"merchant-lib testcase\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:10}\"} ] }"),
+    TALER_TESTING_cmd_merchant_pay_order ("pay-fail-partial-double-10",
+                                          merchant_url,
+                                          MHD_HTTP_CONFLICT,
+                                          "create-proposal-10",
+                                          "withdraw-coin-10a;withdraw-coin-1",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+    TALER_TESTING_cmd_merchant_pay_order ("pay-again-10",
+                                          merchant_url,
+                                          MHD_HTTP_OK,
+                                          "create-proposal-10",
+                                          
"withdraw-coin-10a;withdraw-coin-10b",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+    CMD_EXEC_AGGREGATOR ("run-aggregator-10"),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-9.97-10",
+                                           EXCHANGE_URL,
+                                           "EUR:9.97",
+                                           exchange_payto,
+                                           merchant_payto),
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-10"),
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command pay_abort[] = {
+    cmd_transfer_to_exchange ("create-reserve-11",
+                              "EUR:10.02"),
+    cmd_exec_wirewatch ("wirewatch-11"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-11",
+                                                 "EUR:10.02",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-11"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11a",
+                                       "create-reserve-11",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11b",
+                                       "create-reserve-11",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_status ("withdraw-status-11",
+                              "create-reserve-11",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-11",
+                                            merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"11\",\
+        \"refund_deadline\":{\"t_ms\":0},\
+        \"pay_deadline\":{\"t_ms\":99999999999999},\
+        \"amount\":\"EUR:10.0\",\
+        \"summary\": \"merchant-lib testcase\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:10}\"} ] }"),
+    TALER_TESTING_cmd_merchant_pay_order ("pay-fail-partial-double-11-good",
+                                          merchant_url,
+                                          MHD_HTTP_NOT_ACCEPTABLE,
+                                          "create-proposal-11",
+                                          "withdraw-coin-11a",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+    TALER_TESTING_cmd_merchant_pay_order ("pay-fail-partial-double-11-bad",
+                                          merchant_url,
+                                          MHD_HTTP_CONFLICT,
+                                          "create-proposal-11",
+                                          "withdraw-coin-1",
+                                          "EUR:5",
+                                          "EUR:4.99"),
+    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-11",
+                                            merchant_url,
+                                            "pay-fail-partial-double-11-good",
+                                            MHD_HTTP_OK),
+    CMD_EXEC_AGGREGATOR ("run-aggregator-11"),
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-11"),
+    TALER_TESTING_cmd_end ()
+  };
+
+  const char *payto_uris[] = {
+    PAYTO_I1
+  };
+  struct TALER_TESTING_Command commands[] = {
+    TALER_TESTING_cmd_config ("config",
+                              merchant_url,
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_merchant_get_instances ("instances-empty",
+                                              merchant_url,
+                                              MHD_HTTP_OK,
+                                              NULL),
+    TALER_TESTING_cmd_merchant_post_instances ("instance-create-i1",
+                                               merchant_url,
+                                               "i1",
+                                               PAYTO_I1,
+                                               "EUR",
+                                               MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_get_instances ("instances-get-i1",
+                                              merchant_url,
+                                              MHD_HTTP_OK,
+                                              "instance-create-i1",
+                                              NULL),
+    TALER_TESTING_cmd_merchant_get_instance ("instances-get-i1",
+                                             merchant_url,
+                                             "i1",
+                                             MHD_HTTP_OK,
+                                             "instance-create-i1"),
+    TALER_TESTING_cmd_merchant_patch_instance ("instance-patch-i1",
+                                               merchant_url,
+                                               "i1",
+                                               1,
+                                               payto_uris,
+                                               "bob-the-merchant",
+                                               json_pack ("{s:s}",
+                                                          "street",
+                                                          "bobstreet"),
+                                               json_pack ("{s:s}",
+                                                          "street",
+                                                          "bobjuryst"),
+                                               "EUR:0.1",
+                                               4,
+                                               "EUR:0.5",
+                                               GNUNET_TIME_UNIT_MINUTES,
+                                               GNUNET_TIME_UNIT_MINUTES,
+                                               MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_get_instance ("instances-get-i1-2",
+                                             merchant_url,
+                                             "i1",
+                                             MHD_HTTP_OK,
+                                             "instance-patch-i1"),
+    TALER_TESTING_cmd_merchant_get_instance ("instances-get-i2-nx",
+                                             merchant_url,
+                                             "i2",
+                                             MHD_HTTP_NOT_FOUND,
+                                             NULL),
+    TALER_TESTING_cmd_merchant_post_instances ("instance-create-i2",
+                                               merchant_url,
+                                               "i2",
+                                               PAYTO_I1,
+                                               "EUR",
+                                               MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_delete_instance ("instance-delete-i2",
+                                                merchant_url,
+                                                "i2",
+                                                MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_get_instance ("instances-get-i2-post-deletion",
+                                             merchant_url,
+                                             "i2",
+                                             MHD_HTTP_NOT_FOUND,
+                                             NULL),
+    TALER_TESTING_cmd_merchant_purge_instance ("instance-delete-i2-again",
+                                               merchant_url,
+                                               "i2",
+                                               MHD_HTTP_NOT_FOUND),
+    TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
+                                               merchant_url,
+                                               "default",
+                                               PAYTO_I1,
+                                               "EUR",
+                                               MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_get_products ("get-products-empty",
+                                             merchant_url,
+                                             MHD_HTTP_OK,
+                                             NULL),
+    TALER_TESTING_cmd_merchant_post_products ("post-products-p1",
+                                              merchant_url,
+                                              "product-1",
+                                              "a product",
+                                              "EUR:1",
+                                              MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_get_products ("get-products-p1",
+                                             merchant_url,
+                                             MHD_HTTP_OK,
+                                             "post-products-p1",
+                                             NULL),
+    TALER_TESTING_cmd_merchant_get_product ("get-product-p1",
+                                            merchant_url,
+                                            "product-1",
+                                            MHD_HTTP_OK,
+                                            "post-products-p1"),
+    TALER_TESTING_cmd_merchant_post_products ("post-products-p2",
+                                              merchant_url,
+                                              "product-2",
+                                              "a product",
+                                              "EUR:1",
+                                              MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_patch_product ("patch-products-p2",
+                                              merchant_url,
+                                              "product-2",
+                                              "another product",
+                                              json_pack ("{s:s}", "en", 
"text"),
+                                              "kg",
+                                              "EUR:1",
+                                              json_object (),
+                                              json_object (),
+                                              40,
+                                              0,
+                                              json_pack ("{s:s}",
+                                                         "street",
+                                                         "pstreet"),
+                                              GNUNET_TIME_relative_to_absolute 
(
+                                                GNUNET_TIME_UNIT_MINUTES),
+                                              MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_merchant_get_product ("get-product-p2",
+                                            merchant_url,
+                                            "product-2",
+                                            MHD_HTTP_OK,
+                                            "patch-products-p2"),
+    TALER_TESTING_cmd_merchant_patch_product ("patch-products-p3-nx",
+                                              merchant_url,
+                                              "product-3",
+                                              "nx updated product",
+                                              json_pack ("{s:s}", "en", 
"text"),
+                                              "kg",
+                                              "EUR:1",
+                                              json_object (),
+                                              json_object (),
+                                              40,
+                                              0,
+                                              json_pack ("{s:s}",
+                                                         "street",
+                                                         "pstreet"),
+                                              GNUNET_TIME_relative_to_absolute 
(
+                                                GNUNET_TIME_UNIT_MINUTES),
+                                              MHD_HTTP_NOT_FOUND),
+    TALER_TESTING_cmd_merchant_delete_product ("get-products-empty",
+                                               merchant_url,
+                                               "p1",
+                                               MHD_HTTP_NOT_FOUND),
+    TALER_TESTING_cmd_merchant_delete_product ("get-products-empty",
+                                               merchant_url,
+                                               "product-1",
+                                               MHD_HTTP_NO_CONTENT),
+    TALER_TESTING_cmd_batch ("pay",
+                             pay),
+    TALER_TESTING_cmd_batch ("double-spending",
+                             double_spending),
+    TALER_TESTING_cmd_batch ("pay-again",
+                             pay_again),
+    TALER_TESTING_cmd_batch ("pay-abort",
+                             pay_abort),
+    TALER_TESTING_cmd_batch ("refund",
+                             refund),
+    TALER_TESTING_cmd_batch ("tip",
+                             tip),
+    /**
+     * End the suite.  Fixme: better to have a label for this
+     * too, as it shows a "(null)" token on logs.
+     */
+    TALER_TESTING_cmd_end ()
+  };
+
+  TALER_TESTING_run_with_fakebank (is,
+                                   commands,
+                                   bc.exchange_auth.wire_gateway_url);
+}
+
+
+int
+main (int argc,
+      char *const *argv)
+{
+  unsigned int ret;
+  /* These environment variables get in the way... */
+  unsetenv ("XDG_DATA_HOME");
+  unsetenv ("XDG_CONFIG_HOME");
+
+  GNUNET_log_setup ("test-merchant-api",
+                    "DEBUG",
+                    NULL);
+  if (GNUNET_OK != TALER_TESTING_prepare_fakebank (CONFIG_FILE,
+                                                   "exchange-account-exchange",
+                                                   &bc))
+    return 77;
+
+  payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME);
+  exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME);
+  merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME);
+
+  if (NULL ==
+      (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE)))
+    return 77;
+
+  TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+  switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+                                          GNUNET_YES,
+                                          &ec))
+  {
+  case GNUNET_SYSERR:
+    GNUNET_break (0);
+    return 1;
+  case GNUNET_NO:
+    return 77;
+
+  case GNUNET_OK:
+
+    if (NULL == (merchantd =
+                   TALER_TESTING_run_merchant (CONFIG_FILE,
+                                               merchant_url)))
+      return 1;
+
+    ret = TALER_TESTING_setup_with_exchange (&run,
+                                             NULL,
+                                             CONFIG_FILE);
+
+    GNUNET_OS_process_kill (merchantd, SIGTERM);
+    GNUNET_OS_process_wait (merchantd);
+    GNUNET_OS_process_destroy (merchantd);
+    GNUNET_free (merchant_url);
+
+    if (GNUNET_OK != ret)
+      return 1;
+    break;
+  default:
+    GNUNET_break (0);
+    return 1;
+  }
+  return 0;
+}
+
+
+/* end of test_merchant_api.c */
diff --git a/src/lib/test_merchant_api.conf b/src/testing/test_merchant_api.conf
similarity index 67%
rename from src/lib/test_merchant_api.conf
rename to src/testing/test_merchant_api.conf
index 2bab1ec..955f5c6 100644
--- a/src/lib/test_merchant_api.conf
+++ b/src/testing/test_merchant_api.conf
@@ -30,92 +30,14 @@ HTTP_PORT = 8082
 # Which port do we run the backend on? (HTTP server)
 PORT = 8080
 
-# How quickly do we want the exchange to send us our money?
-# Used only if the frontend does not specify a value.
-WIRE_TRANSFER_DELAY = 0 s
-
 # Which plugin (backend) do we use for the DB.
 DB = postgres
 
-# Default choice for maximum wire fee.
-DEFAULT_MAX_WIRE_FEE = EUR:0.10
-
-# Default choice for maximum deposit fee.
-DEFAULT_MAX_DEPOSIT_FEE = EUR:0.10
-
 
 # This specifies which database the postgres backend uses.
 [merchantdb-postgres]
 CONFIG = postgres:///talercheck
 
-# Different instances operated by this merchant:
-[instance-default]
-KEYFILE = ${TALER_CONFIG_HOME}/merchant/default.priv
-NAME = Kudos Inc.
-
-[instance-tor]
-KEYFILE = ${TALER_CONFIG_HOME}/merchant/tor.priv
-NAME = The Tor Project
-
-
-[instance-tip]
-KEYFILE = ${TALER_CONFIG_HOME}/merchant/tip.priv
-TIP_EXCHANGE = http://localhost:8081/
-TIP_RESERVE_PRIV_FILENAME = ${TALER_CONFIG_HOME}/merchant/reserve/tip.priv
-NAME = Test Tipping Merchant
-
-
-[instance-dtip]
-KEYFILE = ${TALER_CONFIG_HOME}/merchant/dtip.priv
-TIP_EXCHANGE = http://localhost:8081/
-TIP_RESERVE_PRIV_FILENAME = ${TALER_CONFIG_HOME}/merchant/reserve/dtip.priv
-NAME = Test Tipping Merchant 2
-
-[instance-nulltip]
-KEYFILE = ${TALER_CONFIG_HOME}/merchant/nulltip.priv
-TIP_EXCHANGE = http://localhost:8081/
-# This key will NEVER be used to create a reserve, so
-# as to check tip authorization against a non-reserve
-# key.
-TIP_RESERVE_PRIV_FILENAME = ${TALER_CONFIG_HOME}/merchant/reserve/nulltip.priv
-NAME = Test Null-Tipping Merchant
-
-# Account of the MERCHANT
-[merchant-account-merchant]
-# What is the merchant's bank account?
-PAYTO_URI = "payto://x-taler-bank/localhost/3"
-
-# This is the *salted* response we give out for /contract requests.
-# File is generated on first use, no need for merchants to generate
-# the salt!
-WIRE_RESPONSE = ${TALER_CONFIG_HOME}/merchant/account-3.json
-
-# Accept payments to this account in instance-default
-HONOR_default = YES
-
-# Accept payments to this account in instance-tor
-HONOR_tor = YES
-
-# Accept payments to this account in instance-tip
-HONOR_tip = YES
-
-# Accept payments to this account in instance-dtip
-HONOR_dtip = YES
-
-HONOR_nulltip = YES
-
-# Advertise in new contracts of instance-default
-ACTIVE_default = YES
-
-# Advertise in new contracts of instance-default
-ACTIVE_tor = YES
-
-# Advertise in new contracts of instance-default
-ACTIVE_tip = YES
-
-# Advertise in new contracts of instance-default
-ACTIVE_nulltip = YES
-
 # Sections starting with "merchant-exchange-" specify trusted exchanges
 # (by the merchant)
 [merchant-exchange-test]
@@ -123,10 +45,18 @@ MASTER_KEY = 
T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
 EXCHANGE_BASE_URL = http://localhost:8081/
 CURRENCY = EUR
 
-# only fixes skips.
+
+#######################################################
+# Configuration for the auditor for the testcase
+#######################################################
 [auditor]
 BASE_URL = http://the.auditor/
 
+
+#######################################################
+# Configuration for ??? Is this used?
+#######################################################
+
 # Auditors must be in sections "auditor-", the rest of the section
 # name could be anything.
 [auditor-ezb]
diff --git 
a/src/lib/test_merchant_api_home/.config/taler/exchange/account-2.json 
b/src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/exchange/account-2.json
rename to 
src/testing/test_merchant_api_home/.config/taler/exchange/account-2.json
diff --git 
a/src/lib/test_merchant_api_home/.config/taler/merchant/account-3.json 
b/src/testing/test_merchant_api_home/.config/taler/merchant/account-3.json
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/merchant/account-3.json
rename to 
src/testing/test_merchant_api_home/.config/taler/merchant/account-3.json
diff --git a/src/lib/test_merchant_api_home/.config/taler/merchant/default.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/default.priv
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/merchant/default.priv
rename to src/testing/test_merchant_api_home/.config/taler/merchant/default.priv
diff --git a/src/lib/test_merchant_api_home/.config/taler/merchant/dtip.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/dtip.priv
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/merchant/dtip.priv
rename to src/testing/test_merchant_api_home/.config/taler/merchant/dtip.priv
diff --git a/src/lib/test_merchant_api_home/.config/taler/merchant/nulltip.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/nulltip.priv
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/merchant/nulltip.priv
rename to src/testing/test_merchant_api_home/.config/taler/merchant/nulltip.priv
diff --git 
a/src/lib/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv
similarity index 100%
rename from 
src/lib/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv
rename to 
src/testing/test_merchant_api_home/.config/taler/merchant/reserve/dtip.priv
diff --git 
a/src/lib/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv
similarity index 100%
rename from 
src/lib/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv
rename to 
src/testing/test_merchant_api_home/.config/taler/merchant/reserve/nulltip.priv
diff --git 
a/src/lib/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv
similarity index 100%
rename from 
src/lib/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv
rename to 
src/testing/test_merchant_api_home/.config/taler/merchant/reserve/tip.priv
diff --git a/src/lib/test_merchant_api_home/.config/taler/merchant/tip.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/tip.priv
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/merchant/tip.priv
rename to src/testing/test_merchant_api_home/.config/taler/merchant/tip.priv
diff --git a/src/lib/test_merchant_api_home/.config/taler/merchant/tor.priv 
b/src/testing/test_merchant_api_home/.config/taler/merchant/tor.priv
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/merchant/tor.priv
rename to src/testing/test_merchant_api_home/.config/taler/merchant/tor.priv
diff --git a/src/lib/test_merchant_api_home/.config/taler/test.json 
b/src/testing/test_merchant_api_home/.config/taler/test.json
similarity index 100%
rename from src/lib/test_merchant_api_home/.config/taler/test.json
rename to src/testing/test_merchant_api_home/.config/taler/test.json
diff --git 
a/src/lib/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
 
b/src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
similarity index 100%
rename from 
src/lib/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
rename to 
src/testing/test_merchant_api_home/.local/share/taler/exchange/offline-keys/master.priv
diff --git 
a/src/lib/test_merchant_api_home/.local/share/taler/merchant/merchant.priv 
b/src/testing/test_merchant_api_home/.local/share/taler/merchant/merchant.priv
similarity index 100%
rename from 
src/lib/test_merchant_api_home/.local/share/taler/merchant/merchant.priv
rename to 
src/testing/test_merchant_api_home/.local/share/taler/merchant/merchant.priv
diff --git a/src/lib/test_merchant_api_proxy_exchange.conf 
b/src/testing/test_merchant_api_proxy_exchange.conf
similarity index 100%
rename from src/lib/test_merchant_api_proxy_exchange.conf
rename to src/testing/test_merchant_api_proxy_exchange.conf
diff --git a/src/lib/test_merchant_api_proxy_merchant.conf 
b/src/testing/test_merchant_api_proxy_merchant.conf
similarity index 100%
rename from src/lib/test_merchant_api_proxy_merchant.conf
rename to src/testing/test_merchant_api_proxy_merchant.conf
diff --git a/src/lib/test_merchant_api_twisted.c 
b/src/testing/test_merchant_api_twisted.c
similarity index 86%
rename from src/lib/test_merchant_api_twisted.c
rename to src/testing/test_merchant_api_twisted.c
index e49e238..bc6def3 100644
--- a/src/lib/test_merchant_api_twisted.c
+++ b/src/testing/test_merchant_api_twisted.c
@@ -191,6 +191,7 @@ static void
 run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
+  #if 0
   /**** Triggering #5719 ****/
   struct TALER_TESTING_Command bug_5719[] = {
     /**
@@ -216,10 +217,10 @@ run (void *cls,
                               "5719-create-reserve",
                               "EUR:0",
                               MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("5719-create-proposal",
-                                twister_merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("5719-create-proposal",
+                                            twister_merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"5719TRIGGER\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -253,7 +254,7 @@ run (void *cls,
   /**** Covering /check-payment ****/
   struct TALER_TESTING_Command check_payment[] = {
 
-    TALER_TESTING_cmd_proposal
+    TALER_TESTING_cmd_merchant_post_orders
       ("proposal-for-check-payment",
       twister_merchant_url,
       MHD_HTTP_OK,
@@ -305,23 +306,23 @@ run (void *cls,
      */
     TALER_TESTING_cmd_malform_request ("malform-order",
                                        PROXY_MERCHANT_CONFIG_FILE),
-    TALER_TESTING_cmd_proposal ("create-proposal-0",
-                                twister_merchant_url,
-                                MHD_HTTP_BAD_REQUEST,
-                                /* giving a valid JSON to not make it fail 
before
-                                 * data reaches the merchant.  */
-                                "{\"not\": \"used\"}"),
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-0",
+                                            twister_merchant_url,
+                                            MHD_HTTP_BAD_REQUEST,
+                                            /* giving a valid JSON to not make 
it fail before
+                                             * data reaches the merchant.  */
+                                            "{\"not\": \"used\"}"),
     TALER_TESTING_cmd_hack_response_code ("proposal-500",
                                           PROXY_MERCHANT_CONFIG_FILE,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR),
-    TALER_TESTING_cmd_proposal ("create-proposal-1",
-                                twister_merchant_url,
-                                /* This status code == 0 is gotten via a 500 
Internal Server
-                                 * Error handed to the library.  */
-                                MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                /* giving a valid JSON to not make it fail 
before
-                                 * data reaches the merchant.  */
-                                "{\"not\": \"used\"}"),
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1",
+                                            twister_merchant_url,
+                                            /* This status code == 0 is gotten 
via a 500 Internal Server
+                                             * Error handed to the library.  */
+                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                            /* giving a valid JSON to not make 
it fail before
+                                             * data reaches the merchant.  */
+                                            "{\"not\": \"used\"}"),
 
     /**
      * Cause the PUT /proposal callback to be called
@@ -331,10 +332,10 @@ run (void *cls,
     TALER_TESTING_cmd_malform_response ("malform-proposal",
                                         PROXY_MERCHANT_CONFIG_FILE),
 
-    TALER_TESTING_cmd_proposal ("create-proposal-2",
-                                twister_merchant_url,
-                                0,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
+                                            twister_merchant_url,
+                                            0,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"1\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -349,10 +350,10 @@ run (void *cls,
     TALER_TESTING_cmd_delete_object ("remove-order-id",
                                      PROXY_MERCHANT_CONFIG_FILE,
                                      "order_id"),
-    TALER_TESTING_cmd_proposal ("create-proposal-3",
-                                twister_merchant_url,
-                                0,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-3",
+                                            twister_merchant_url,
+                                            0,
+                                            "{\"max_fee\":\"EUR:0.5\",\
          \"fulfillment_url\": \"https://example.com/\",\
          \"order_id\":\"2\",\
          \"refund_deadline\":{\"t_ms\":0},\
@@ -365,31 +366,31 @@ run (void *cls,
      * Cause a 404 Not Found response code,
      * due to a non existing merchant instance.
      */
-    TALER_TESTING_cmd_proposal ("create-proposal-4",
-                                twister_merchant_url_instance_nonexistent,
-                                MHD_HTTP_NOT_FOUND,
-                                "{\"amount\":\"EUR:5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-4",
+                                            
twister_merchant_url_instance_nonexistent,
+                                            MHD_HTTP_NOT_FOUND,
+                                            "{\"amount\":\"EUR:5\",\
          \"fulfillment_url\": \"https://example.com/\",\
          \"summary\": \"merchant-lib testcase\"}"),
 
     /* Cause a 404 Not Found from /proposal/lookup,
      * due to a non existing order id being queried.  */
-    TALER_TESTING_cmd_proposal_lookup ("lookup-0",
-                                       twister_merchant_url,
-                                       MHD_HTTP_NOT_FOUND,
-                                       NULL,
-                                       "does-not-exist"),
+    TALER_TESTING_cmd_merchant_post_orders_lookup ("lookup-0",
+                                                   twister_merchant_url,
+                                                   MHD_HTTP_NOT_FOUND,
+                                                   NULL,
+                                                   "does-not-exist"),
     /* Cause a unparsable response to be returned.  */
     TALER_TESTING_cmd_malform_response
       ("malform-proposal-lookup",
       PROXY_MERCHANT_CONFIG_FILE),
     /* To be short, we'll make a _error_ response to be
      * unparsable.  */
-    TALER_TESTING_cmd_proposal_lookup ("lookup-1",
-                                       twister_merchant_url,
-                                       0, // response code.
-                                       NULL,
-                                       "does-not-exist"),
+    TALER_TESTING_cmd_merchant_post_orders_lookup ("lookup-1",
+                                                   twister_merchant_url,
+                                                   0, // response code.
+                                                   NULL,
+                                                   "does-not-exist"),
 
     /* Generating a proposal-lookup response which doesn't pass
      * validation, by removing a field that is expected by the
@@ -398,10 +399,10 @@ run (void *cls,
 
     /* First step is to create a _valid_ proposal, so that
      * we can lookup for it later.  */
-    TALER_TESTING_cmd_proposal ("create-proposal-5",
-                                twister_merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-5",
+                                            twister_merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"5\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -417,12 +418,12 @@ run (void *cls,
                                      "contract_terms"),
 
     /* lookup!  */
-    TALER_TESTING_cmd_proposal_lookup ("lookup-5",
-                                       twister_merchant_url,
-                                       // expected response code.
-                                       0,
-                                       "create-proposal-5",
-                                       NULL),
+    TALER_TESTING_cmd_merchant_post_orders_lookup ("lookup-5",
+                                                   twister_merchant_url,
+                                                   // expected response code.
+                                                   0,
+                                                   "create-proposal-5",
+                                                   NULL),
     TALER_TESTING_cmd_end ()
   };
 
@@ -486,14 +487,14 @@ run (void *cls,
                                        "create-reserve-unaggregation",
                                        "EUR:5",
                                        MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-unaggregation",
-                                /* Need a fresh instance in order to associate 
this
-                                 * proposal with a fresh h_wire;  this way, 
this proposal
-                                 * won't get hooked by the aggregator 
gathering same-h_wire'd
-                                 * transactions.  */
-                                twister_merchant_url_instance_tor,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-unaggregation",
+                                            /* Need a fresh instance in order 
to associate this
+                                             * proposal with a fresh h_wire;  
this way, this proposal
+                                             * won't get hooked by the 
aggregator gathering same-h_wire'd
+                                             * transactions.  */
+                                            twister_merchant_url_instance_tor,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"refund_deadline\":{\"t_ms\":2000},\
         \"pay_deadline\":{\"t_ms\":2366841500000},\
         \"wire_transfer_deadline\":{\"t_ms\":2366841600000},\
@@ -535,10 +536,10 @@ run (void *cls,
                                        "create-reserve-5383",
                                        "EUR:1",
                                        MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-5383",
-                                twister_merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-5383",
+                                            twister_merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"5383\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -610,10 +611,10 @@ run (void *cls,
                               "create-reserve-1",
                               "EUR:0",
                               MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-6",
-                                twister_merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-6",
+                                            twister_merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"11\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -707,10 +708,10 @@ run (void *cls,
                               "create-reserve-abort-1",
                               "EUR:0",
                               MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal ("create-proposal-abort-1",
-                                twister_merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-abort-1",
+                                            twister_merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"abort-one\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -769,10 +770,10 @@ run (void *cls,
     CMD_TRANSFER_TO_EXCHANGE ("create-reserve-double-spend",
                               "EUR:1.01"),
     CMD_EXEC_WIREWATCH ("wirewatch-double-spend"),
-    TALER_TESTING_cmd_proposal ("create-proposal-double-spend",
-                                twister_merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-double-spend",
+                                            twister_merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"DS-1\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -780,10 +781,10 @@ run (void *cls,
         \"amount\":\"EUR:1.0\",\
         \"summary\": \"merchant-lib testcase\",\
         \"products\": [ {\"description\": \"will succeed\"}] }"),
-    TALER_TESTING_cmd_proposal ("create-proposal-double-spend-1",
-                                twister_merchant_url,
-                                MHD_HTTP_OK,
-                                "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-double-spend-1",
+                                            twister_merchant_url,
+                                            MHD_HTTP_OK,
+                                            "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"DS-2\",\
         \"refund_deadline\":{\"t_ms\":0},\
         \"pay_deadline\":{\"t_ms\":\"never\"},\
@@ -820,9 +821,10 @@ run (void *cls,
     /* max uint64 number: 9223372036854775807; try to overflow! */
     TALER_TESTING_cmd_end ()
   };
+  #endif
 
   struct TALER_TESTING_Command commands[] = {
-    TALER_TESTING_cmd_batch ("check-payment",
+    /*TALER_TESTING_cmd_batch ("check-payment",
                              check_payment),
     TALER_TESTING_cmd_batch ("proposal",
                              proposal),
@@ -837,7 +839,7 @@ run (void *cls,
     TALER_TESTING_cmd_batch ("pay",
                              pay),
     TALER_TESTING_cmd_batch ("bug-5719",
-                             bug_5719),
+                             bug_5719),*/
     TALER_TESTING_cmd_end ()
   };
 
diff --git a/src/lib/test_merchant_api_twisted.conf 
b/src/testing/test_merchant_api_twisted.conf
similarity index 100%
rename from src/lib/test_merchant_api_twisted.conf
rename to src/testing/test_merchant_api_twisted.conf
diff --git a/src/testing/testing_api_cmd_abort_order.c 
b/src/testing/testing_api_cmd_abort_order.c
new file mode 100644
index 0000000..c44684e
--- /dev/null
+++ b/src/testing/testing_api_cmd_abort_order.c
@@ -0,0 +1,448 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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 lib/testing_api_cmd_abort_order.c
+ * @brief command to test the abort feature.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+#define AMOUNT_WITH_FEE 0
+
+/**
+ * State for a " abort" CMD.
+ */
+struct AbortState
+{
+
+  /**
+   * Reference to the "pay" command to abort.
+   */
+  const char *pay_reference;
+
+  /**
+   * Merchant URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * Handle to a "abort" operation.
+   */
+  struct TALER_MERCHANT_OrderAbortHandle *oah;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * The actual abort/refund data.
+   */
+  struct TALER_MERCHANT_AbortedCoin *acs;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * How many refund permissions this CMD got
+   * the right for.  Roughly, there is one refund
+   * permission for one coin.
+   */
+  unsigned int acs_length;
+
+};
+
+
+/**
+ * Parse the @a coins specification and grow the @a ac
+ * array with the coins found, updating @a nac.
+ *
+ * @param[in,out] ac pointer to array of coins found
+ * @param[in,out] nac length of array at @a pc
+ * @param[in] coins string specifying coins to add to @a pc,
+ *            clobbered in the process
+ * @param is interpreter state
+ * @param amount_with_fee total amount to be paid for a contract.
+ * @param amount_without_fee to be removed, there is no
+ *        per-contract fee, only per-coin exists.
+ * @param refund_fee per-contract? per-coin?
+ * @return #GNUNET_OK on success
+ */
+static int
+build_coins (struct TALER_MERCHANT_AbortCoin **ac,
+             unsigned int *nac,
+             char *coins,
+             struct TALER_TESTING_Interpreter *is,
+             const char *amount_with_fee)
+{
+  char *token;
+
+  for (token = strtok (coins, ";");
+       NULL != token;
+       token = strtok (NULL, ";"))
+  {
+    char *ctok;
+    unsigned int ci;
+    struct TALER_MERCHANT_AbortCoin *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);
+        return GNUNET_SYSERR;
+      }
+    }
+    // FIXME: ci not used!?
+    {
+      const struct TALER_TESTING_Command *coin_cmd;
+      coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                           token);
+      if (NULL == coin_cmd)
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      GNUNET_array_grow (*ac,
+                         *nac,
+                         (*nac) + 1);
+      icoin = &((*ac)[(*nac) - 1]);
+
+      {
+        const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_TESTING_get_trait_coin_priv (coin_cmd,
+                                                          0,
+                                                          &coin_priv));
+        GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+                                            &icoin->coin_pub.eddsa_pub);
+      }
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_TESTING_get_trait_url (coin_cmd,
+                                                  
TALER_TESTING_UT_EXCHANGE_BASE_URL,
+                                                  &icoin->exchange_url));
+      {
+        const struct TALER_Amount *denom_value;
+        GNUNET_assert (GNUNET_OK
+                       == TALER_TESTING_get_trait_amount_obj (coin_cmd,
+                                                              0,
+                                                              &denom_value));
+        icoin->amount_with_fee = *denom_value;
+      }
+
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Callback for a "pay abort" operation.  Mainly, check HTTP
+ * response code was as expected and stores refund permissions
+ * in the state.
+ *
+ * @param cls closure.
+ * @param hr HTTP response
+ * @param merchant_pub public key of the merchant refunding the
+ *        contract.
+ * @param h_contract the contract involved in the refund.
+ * @param num_refunds length of the @a res array
+ * @param res array containing the abort confirmations
+ */
+static void
+abort_cb (void *cls,
+          const struct TALER_MERCHANT_HttpResponse *hr,
+          const struct TALER_MerchantPublicKeyP *merchant_pub,
+          unsigned int num_aborts,
+          const struct TALER_MERCHANT_AbortedCoin res[])
+{
+  struct AbortState *as = cls;
+
+  as->oah = NULL;
+  if (as->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (as->is));
+    TALER_TESTING_FAIL (as->is);
+  }
+  if ( (MHD_HTTP_OK == hr->http_status) &&
+       (TALER_EC_NONE == hr->ec) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Received %u refunds\n",
+                num_aborts);
+    as->acs_length = num_aborts;
+    as->acs = GNUNET_new_array (num_aborts,
+                                struct TALER_MERCHANT_AbortedCoin);
+    memcpy (as->acs,
+            res,
+            num_aborts * sizeof (struct TALER_MERCHANT_AbortedCoin));
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Successful pay-abort (HTTP status: %u)\n",
+              hr->http_status);
+  TALER_TESTING_interpreter_next (as->is);
+}
+
+
+/**
+ * Run an "abort" CMD.
+ *
+ * @param cls closure
+ * @param cmd command being run.
+ * @param is interpreter state
+ */
+static void
+abort_run (void *cls,
+           const struct TALER_TESTING_Command *cmd,
+           struct TALER_TESTING_Interpreter *is)
+{
+  struct AbortState *as = cls;
+  const struct TALER_TESTING_Command *pay_cmd;
+  const char *proposal_reference;
+  const char *coin_reference;
+  const char *amount_with_fee;
+  const struct TALER_TESTING_Command *proposal_cmd;
+  const char *order_id;
+  const struct GNUNET_HashCode *h_proposal;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct TALER_Amount total_amount;
+  const char *error_name;
+  unsigned int error_line;
+  struct TALER_MERCHANT_AbortCoin *abort_coins;
+  unsigned int nabort_coins;
+  char *cr;
+
+  as->is = is;
+  pay_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                      as->pay_reference);
+  if (NULL == pay_cmd)
+    TALER_TESTING_FAIL (is);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_proposal_reference (pay_cmd,
+                                                  0,
+                                                  &proposal_reference))
+    TALER_TESTING_FAIL (is);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_coin_reference (pay_cmd,
+                                              0,
+                                              &coin_reference))
+    TALER_TESTING_FAIL (is);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_string (pay_cmd,
+                                      AMOUNT_WITH_FEE,
+                                      &amount_with_fee))
+    TALER_TESTING_FAIL (is);
+  proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                           proposal_reference);
+
+  if (NULL == proposal_cmd)
+    TALER_TESTING_FAIL (is);
+
+  {
+    const json_t *contract_terms;
+
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_contract_terms (proposal_cmd,
+                                                0,
+                                                &contract_terms))
+      TALER_TESTING_FAIL (is);
+    {
+      /* Get information that needs to be put verbatim in the
+       * deposit permission */
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_string ("order_id",
+                                 &order_id),
+        GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                     &merchant_pub),
+        TALER_JSON_spec_amount ("amount",
+                                &total_amount),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (contract_terms,
+                             spec,
+                             &error_name,
+                             &error_line))
+      {
+        char *js;
+
+        js = json_dumps (contract_terms,
+                         JSON_INDENT (1));
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Parser failed on %s:%u for input `%s'\n",
+                    error_name,
+                    error_line,
+                    js);
+        free (js);
+        TALER_TESTING_FAIL (is);
+      }
+    }
+  }
+
+  cr = GNUNET_strdup (coin_reference);
+  abort_coins = NULL;
+  nabort_coins = 0;
+  if (GNUNET_OK !=
+      build_coins (&abort_coins,
+                   &nabort_coins,
+                   cr,
+                   is,
+                   amount_with_fee))
+  {
+    GNUNET_array_grow (abort_coins,
+                       nabort_coins,
+                       0);
+    GNUNET_free (cr);
+    TALER_TESTING_FAIL (is);
+  }
+  GNUNET_free (cr);
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
+                                                0,
+                                                &h_proposal))
+    TALER_TESTING_FAIL (is);
+  as->oah = TALER_MERCHANT_order_abort (is->ctx,
+                                        as->merchant_url,
+                                        order_id,
+                                        &merchant_pub,
+                                        h_proposal,
+                                        nabort_coins,
+                                        abort_coins,
+                                        &abort_cb,
+                                        as);
+  GNUNET_array_grow (abort_coins,
+                     nabort_coins,
+                     0);
+  if (NULL == as->oah)
+    TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Free a "pay abort" CMD, and cancel it if need be.
+ *
+ * @param cls closure.
+ * @param cmd command currently being freed.
+ */
+static void
+abort_cleanup (void *cls,
+               const struct TALER_TESTING_Command *cmd)
+{
+  struct AbortState *as = cls;
+
+  if (NULL != as->oah)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command `%s' did not complete.\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  as->is));
+    TALER_MERCHANT_order_abort_cancel (as->oah);
+  }
+  GNUNET_array_grow (as->acs,
+                     as->acs_length,
+                     0);
+  GNUNET_free (as);
+}
+
+
+/**
+ * Offer internal data useful to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+abort_traits (void *cls,
+              const void **ret,
+              const char *trait,
+              unsigned int index)
+{
+  struct AbortState *as = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_trait_end ()
+  };
+
+  (void) as;
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Make an "abort" test command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to abort
+ * @param http_status expected HTTP response code
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_order_abort (const char *label,
+                                        const char *merchant_url,
+                                        const char *pay_reference,
+                                        unsigned int http_status)
+{
+  struct AbortState *as;
+
+  as = GNUNET_new (struct AbortState);
+  as->http_status = http_status;
+  as->pay_reference = pay_reference;
+  as->merchant_url = merchant_url;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = as,
+      .label = label,
+      .run = &abort_run,
+      .cleanup = &abort_cleanup,
+      .traits = &abort_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_abort_order.c */
diff --git a/src/lib/testing_api_cmd_proposal_lookup.c 
b/src/testing/testing_api_cmd_claim_order.c
similarity index 63%
rename from src/lib/testing_api_cmd_proposal_lookup.c
rename to src/testing/testing_api_cmd_claim_order.c
index 1ea0c0c..f47ddcc 100644
--- a/src/lib/testing_api_cmd_proposal_lookup.c
+++ b/src/testing/testing_api_cmd_claim_order.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2018 Taler Systems SA
+  Copyright (C) 2014-2018, 2020 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
@@ -18,8 +18,8 @@
 */
 
 /**
- * @file exchange/testing_api_cmd_proposal_lookup.c
- * @brief command to execute a proposal lookup
+ * @file exchange/testing_api_cmd_claim_order.c
+ * @brief command to claim an order
  * @author Marcello Stanisci
  */
 #include "platform.h"
@@ -29,10 +29,10 @@
 #include "taler_merchant_testing_lib.h"
 
 /**
- * State for a "proposal lookup" CMD.  Not used by
- * the initial lookup operation.
+ * State for a "order claim" CMD.  Not used by
+ * the initial claim operation.
  */
-struct ProposalLookupState
+struct OrderClaimState
 {
   /**
    * The interpreter state.
@@ -70,18 +70,18 @@ struct ProposalLookupState
   unsigned int http_status;
 
   /**
-   * /proposal/lookup operation handle.
+   * /order/claim operation handle.
    */
-  struct TALER_MERCHANT_ProposalLookupOperation *plo;
+  struct TALER_MERCHANT_OrderClaimHandle *och;
 
   /**
-   * Reference to a proposal operation.  Will offer the
+   * Reference to a order operation.  Will offer the
    * nonce for the operation.
    */
-  const char *proposal_reference;
+  const char *order_reference;
 
   /**
-   * Order id to lookup upon.  If null, the @a proposal_reference
+   * Order id to claim upon.  If null, the @a order_reference
    * will offer this value.
    */
   const char *order_id;
@@ -89,25 +89,25 @@ struct ProposalLookupState
 
 
 /**
- * Free the state of a "proposal lookup" CMD, and possibly
+ * Free the state of a "order claim" CMD, and possibly
  * cancel it if it did not complete.
  *
  * @param cls closure.
  * @param cmd command being freed.
  */
 static void
-proposal_lookup_cleanup (void *cls,
-                         const struct TALER_TESTING_Command *cmd)
+order_claim_cleanup (void *cls,
+                     const struct TALER_TESTING_Command *cmd)
 {
-  struct ProposalLookupState *pls = cls;
+  struct OrderClaimState *pls = cls;
 
-  if (NULL != pls->plo)
+  if (NULL != pls->och)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Command '%s' did not complete\n",
                 cmd->label);
-    TALER_MERCHANT_proposal_lookup_cancel (pls->plo);
-    pls->plo = NULL;
+    TALER_MERCHANT_order_claim_cancel (pls->och);
+    pls->och = NULL;
   }
   if (NULL != pls->contract_terms)
   {
@@ -119,27 +119,27 @@ proposal_lookup_cleanup (void *cls,
 
 
 /**
- * Callback for "proposal lookup" operation, to check the
+ * Callback for "order claim" operation, to check the
  * response code is as expected.
  *
  * @param cls closure
  * @param hr HTTP response we got
  * @param contract_terms the contract terms; they are the
- *        backend-filled up proposal minus cryptographic
+ *        backend-filled up order minus cryptographic
  *        information.
  * @param sig merchant signature over the contract terms.
  * @param hash hash code of the contract terms.
  */
 static void
-proposal_lookup_cb (void *cls,
-                    const struct TALER_MERCHANT_HttpResponse *hr,
-                    const json_t *contract_terms,
-                    const struct TALER_MerchantSignatureP *sig,
-                    const struct GNUNET_HashCode *hash)
+order_claim_cb (void *cls,
+                const struct TALER_MERCHANT_HttpResponse *hr,
+                const json_t *contract_terms,
+                const struct TALER_MerchantSignatureP *sig,
+                const struct GNUNET_HashCode *hash)
 {
-  struct ProposalLookupState *pls = cls;
+  struct OrderClaimState *pls = cls;
 
-  pls->plo = NULL;
+  pls->och = NULL;
   if (pls->http_status != hr->http_status)
     TALER_TESTING_FAIL (pls->is);
   if (MHD_HTTP_OK == hr->http_status)
@@ -173,27 +173,24 @@ proposal_lookup_cb (void *cls,
 
 
 /**
- * Run the "proposal lookup" CMD.
+ * Run the "order claim" CMD.
  *
  * @param cls closure.
  * @param cmd command currently being run.
  * @param is interpreter state.
  */
 static void
-proposal_lookup_run (void *cls,
-                     const struct TALER_TESTING_Command *cmd,
-                     struct TALER_TESTING_Interpreter *is)
+order_claim_run (void *cls,
+                 const struct TALER_TESTING_Command *cmd,
+                 struct TALER_TESTING_Interpreter *is)
 {
-  struct ProposalLookupState *pls = cls;
+  struct OrderClaimState *pls = cls;
   const char *order_id;
-  const struct TALER_MerchantPublicKeyP *nonce;
+  const struct GNUNET_CRYPTO_EddsaPublicKey *nonce;
   /* Only used if we do NOT use the nonce from traits.  */
-  struct TALER_MerchantPublicKeyP dummy_nonce;
-  #define GET_TRAIT_NONCE(cmd,ptr) \
-  TALER_TESTING_get_trait_merchant_pub (cmd, 1, ptr)
+  struct GNUNET_CRYPTO_EddsaPublicKey dummy_nonce;
 
   pls->is = is;
-
   if (NULL != pls->order_id)
   {
     order_id = pls->order_id;
@@ -204,16 +201,17 @@ proposal_lookup_run (void *cls,
   }
   else
   {
-    const struct TALER_TESTING_Command *proposal_cmd;
-
-    proposal_cmd = TALER_TESTING_interpreter_lookup_command
-                     (is, pls->proposal_reference);
+    const struct TALER_TESTING_Command *order_cmd;
 
-    if (NULL == proposal_cmd)
+    order_cmd
+      = TALER_TESTING_interpreter_lookup_command (is,
+                                                  pls->order_reference);
+    if (NULL == order_cmd)
       TALER_TESTING_FAIL (is);
-
-    if (GNUNET_OK != GET_TRAIT_NONCE (proposal_cmd,
-                                      &nonce))
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_claim_nonce (order_cmd,
+                                             0,
+                                             &nonce))
     {
       GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                                   &dummy_nonce,
@@ -221,17 +219,19 @@ proposal_lookup_run (void *cls,
       nonce = &dummy_nonce;
     }
 
-    if (GNUNET_OK != TALER_TESTING_get_trait_order_id
-          (proposal_cmd, 0, &order_id))
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_order_id (order_cmd,
+                                          0,
+                                          &order_id))
       TALER_TESTING_FAIL (is);
   }
-  pls->plo = TALER_MERCHANT_proposal_lookup (is->ctx,
-                                             pls->merchant_url,
-                                             order_id,
-                                             &nonce->eddsa_pub,
-                                             &proposal_lookup_cb,
-                                             pls);
-  GNUNET_assert (NULL != pls->plo);
+  pls->och = TALER_MERCHANT_order_claim (is->ctx,
+                                         pls->merchant_url,
+                                         order_id,
+                                         nonce,
+                                         &order_claim_cb,
+                                         pls);
+  GNUNET_assert (NULL != pls->och);
 }
 
 
@@ -245,12 +245,12 @@ proposal_lookup_run (void *cls,
  * @return #GNUNET_OK on success
  */
 static int
-proposal_lookup_traits (void *cls,
-                        const void **ret,
-                        const char *trait,
-                        unsigned int index)
+order_claim_traits (void *cls,
+                    const void **ret,
+                    const char *trait,
+                    unsigned int index)
 {
-  struct ProposalLookupState *pls = cls;
+  struct OrderClaimState *pls = cls;
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_contract_terms (0,
                                              pls->contract_terms),
@@ -271,39 +271,38 @@ proposal_lookup_traits (void *cls,
 
 
 /**
- * Make a "proposal lookup" command.
+ * Make a "order claim" command.
  *
  * @param label command label.
  * @param merchant_url base URL of the merchant backend
- *        serving the proposal lookup request.
+ *        serving the order claim request.
  * @param http_status expected HTTP response code.
- * @param proposal_reference reference to a "proposal" CMD.
- * @param order_id order id to lookup, can be NULL.
- *
+ * @param order_reference reference to a POST order CMD, can be NULL if @a 
order_id given
+ * @param order_id order id to lookup, can be NULL (then we use @a 
order_reference)
  * @return the command.
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_proposal_lookup
-  (const char *label,
+TALER_TESTING_cmd_merchant_claim_order (
+  const char *label,
   const char *merchant_url,
   unsigned int http_status,
-  const char *proposal_reference,
+  const char *order_reference,
   const char *order_id)
 {
-  struct ProposalLookupState *pls;
+  struct OrderClaimState *pls;
 
-  pls = GNUNET_new (struct ProposalLookupState);
+  pls = GNUNET_new (struct OrderClaimState);
   pls->http_status = http_status;
-  pls->proposal_reference = proposal_reference;
+  pls->order_reference = order_reference;
   pls->merchant_url = merchant_url;
   pls->order_id = order_id;
   {
     struct TALER_TESTING_Command cmd = {
       .cls = pls,
       .label = label,
-      .run = &proposal_lookup_run,
-      .cleanup = &proposal_lookup_cleanup,
-      .traits = &proposal_lookup_traits
+      .run = &order_claim_run,
+      .cleanup = &order_claim_cleanup,
+      .traits = &order_claim_traits
     };
 
     return cmd;
diff --git a/src/lib/testing_api_cmd_config.c 
b/src/testing/testing_api_cmd_config.c
similarity index 100%
rename from src/lib/testing_api_cmd_config.c
rename to src/testing/testing_api_cmd_config.c
diff --git a/src/testing/testing_api_cmd_delete_instance.c 
b/src/testing/testing_api_cmd_delete_instance.c
new file mode 100644
index 0000000..d06b331
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_instance.c
@@ -0,0 +1,230 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_delete_instance.c
+ * @brief command to test DELETE /instance/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "DELETE /instances/$ID" CMD.
+ */
+struct DeleteInstanceState
+{
+
+  /**
+   * Handle for a "DELETE instance" request.
+   */
+  struct TALER_MERCHANT_InstanceDeleteHandle *igh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the instance to run DELETE for.
+   */
+  const char *instance_id;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Use purge, not delete.
+   */
+  bool purge;
+
+};
+
+
+/**
+ * Callback for a /delete/instances/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+delete_instance_cb (void *cls,
+                    const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct DeleteInstanceState *dis = cls;
+
+  dis->igh = NULL;
+  if (dis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (dis->is));
+    TALER_TESTING_interpreter_fail (dis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (dis->is);
+}
+
+
+/**
+ * Run the "DELETE instance" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_instance_run (void *cls,
+                     const struct TALER_TESTING_Command *cmd,
+                     struct TALER_TESTING_Interpreter *is)
+{
+  struct DeleteInstanceState *dis = cls;
+
+  dis->is = is;
+  if (dis->purge)
+    dis->igh = TALER_MERCHANT_instance_purge (is->ctx,
+                                              dis->merchant_url,
+                                              dis->instance_id,
+                                              &delete_instance_cb,
+                                              dis);
+  else
+    dis->igh = TALER_MERCHANT_instance_delete (is->ctx,
+                                               dis->merchant_url,
+                                               dis->instance_id,
+                                               &delete_instance_cb,
+                                               dis);
+  GNUNET_assert (NULL != dis->igh);
+}
+
+
+/**
+ * Free the state of a "DELETE instance" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_instance_cleanup (void *cls,
+                         const struct TALER_TESTING_Command *cmd)
+{
+  struct DeleteInstanceState *dis = cls;
+
+  if (NULL != dis->igh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "DELETE /instances/$ID operation did not complete\n");
+    TALER_MERCHANT_instance_delete_cancel (dis->igh);
+  }
+  GNUNET_free (dis);
+}
+
+
+/**
+ * Define a "DELETE instance" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /instances/$ID request.
+ * @param instance_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_instance (const char *label,
+                                            const char *merchant_url,
+                                            const char *instance_id,
+                                            unsigned int http_status)
+{
+  struct DeleteInstanceState *dis;
+
+  dis = GNUNET_new (struct DeleteInstanceState);
+  dis->merchant_url = merchant_url;
+  dis->instance_id = instance_id;
+  dis->http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = dis,
+      .label = label,
+      .run = &delete_instance_run,
+      .cleanup = &delete_instance_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Define a "PURGE instance" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        PURGE /instances/$ID request.
+ * @param instance_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_purge_instance (const char *label,
+                                           const char *merchant_url,
+                                           const char *instance_id,
+                                           unsigned int http_status)
+{
+  struct DeleteInstanceState *dis;
+
+  dis = GNUNET_new (struct DeleteInstanceState);
+  dis->merchant_url = merchant_url;
+  dis->instance_id = instance_id;
+  dis->http_status = http_status;
+  dis->purge = true;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = dis,
+      .label = label,
+      .run = &delete_instance_run,
+      .cleanup = &delete_instance_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_delete_instance.c */
diff --git a/src/testing/testing_api_cmd_delete_order.c 
b/src/testing/testing_api_cmd_delete_order.c
new file mode 100644
index 0000000..6a86c17
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_order.c
@@ -0,0 +1,179 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_delete_order.c
+ * @brief command to test DELETE /orders/$ORDER_ID
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "DELETE /order/$ORDER_ID" CMD.
+ */
+struct DeleteOrderState
+{
+
+  /**
+   * Handle for a "DELETE order" request.
+   */
+  struct TALER_MERCHANT_OrderDeleteHandle *odh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the order to run DELETE for.
+   */
+  const char *order_id;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a DELETE /orders/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+delete_order_cb (void *cls,
+                 const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct DeleteOrderState *dos = cls;
+
+  dos->odh = NULL;
+  if (dos->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (dos->is));
+    TALER_TESTING_interpreter_fail (dos->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (dos->is);
+}
+
+
+/**
+ * Run the "DELETE order" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_order_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct DeleteOrderState *dos = cls;
+
+  dos->is = is;
+  dos->odh = TALER_MERCHANT_order_delete (is->ctx,
+                                          dos->merchant_url,
+                                          dos->order_id,
+                                          &delete_order_cb,
+                                          dos);
+  GNUNET_assert (NULL != dos->odh);
+}
+
+
+/**
+ * Free the state of a "DELETE order" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_order_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct DeleteOrderState *dos = cls;
+
+  if (NULL != dos->odh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "DELETE /orders/$ORDER_ID operation did not complete\n");
+    TALER_MERCHANT_order_delete_cancel (dos->odh);
+  }
+  GNUNET_free (dos);
+}
+
+
+/**
+ * Define a "DELETE order" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /instances/$ID request.
+ * @param order_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_order (const char *label,
+                                         const char *merchant_url,
+                                         const char *order_id,
+                                         unsigned int http_status)
+{
+  struct DeleteOrderState *dos;
+
+  dos = GNUNET_new (struct DeleteOrderState);
+  dos->merchant_url = merchant_url;
+  dos->order_id = order_id;
+  dos->http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = dos,
+      .label = label,
+      .run = &delete_order_run,
+      .cleanup = &delete_order_cleanup
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_delete_product.c 
b/src/testing/testing_api_cmd_delete_product.c
new file mode 100644
index 0000000..f306c76
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_product.c
@@ -0,0 +1,182 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_delete_product.c
+ * @brief command to test DELETE /product/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "DELETE /products/$ID" CMD.
+ */
+struct DeleteProductState
+{
+
+  /**
+   * Handle for a "DELETE product" request.
+   */
+  struct TALER_MERCHANT_ProductDeleteHandle *pdh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the product to run DELETE for.
+   */
+  const char *product_id;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /delete/products/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+delete_product_cb (void *cls,
+                   const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct DeleteProductState *dis = cls;
+
+  dis->pdh = NULL;
+  if (dis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (dis->is));
+    TALER_TESTING_interpreter_fail (dis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (dis->is);
+}
+
+
+/**
+ * Run the "DELETE product" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_product_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct DeleteProductState *dis = cls;
+
+  dis->is = is;
+  dis->pdh = TALER_MERCHANT_product_delete (is->ctx,
+                                            dis->merchant_url,
+                                            dis->product_id,
+                                            &delete_product_cb,
+                                            dis);
+  GNUNET_assert (NULL != dis->pdh);
+}
+
+
+/**
+ * Free the state of a "DELETE product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_product_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct DeleteProductState *dis = cls;
+
+  if (NULL != dis->pdh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "DELETE /products/$ID operation did not complete\n");
+    TALER_MERCHANT_product_delete_cancel (dis->pdh);
+  }
+  GNUNET_free (dis);
+}
+
+
+/**
+ * Define a "DELETE product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_product (const char *label,
+                                           const char *merchant_url,
+                                           const char *product_id,
+                                           unsigned int http_status)
+{
+  struct DeleteProductState *dis;
+
+  dis = GNUNET_new (struct DeleteProductState);
+  dis->merchant_url = merchant_url;
+  dis->product_id = product_id;
+  dis->http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = dis,
+      .label = label,
+      .run = &delete_product_run,
+      .cleanup = &delete_product_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_delete_product.c */
diff --git a/src/testing/testing_api_cmd_delete_reserve.c 
b/src/testing/testing_api_cmd_delete_reserve.c
new file mode 100644
index 0000000..fe746d4
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_reserve.c
@@ -0,0 +1,245 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_delete_reserve.c
+ * @brief command to test DELETE /reserves/$RESERVE_PUB
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "DELETE /reserves/$RESERVE_PUB" CMD.
+ */
+struct DeleteReserveState
+{
+
+  /**
+   * Handle for a "DELETE reserve" request.
+   */
+  struct TALER_MERCHANT_ReserveDeleteHandle *rdh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * Reference to a command that provides a reserve.
+   */
+  const char *reserve_reference;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Use purge, not delete.
+   */
+  bool purge;
+
+};
+
+
+/**
+ * Callback for a DELETE /reserves/$RESERVE_PUB operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+delete_reserve_cb (void *cls,
+                   const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct DeleteReserveState *drs = cls;
+
+  drs->rdh = NULL;
+  if (drs->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (drs->is));
+    TALER_TESTING_interpreter_fail (drs->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    break;
+  case MHD_HTTP_CONFLICT:
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (drs->is);
+}
+
+
+/**
+ * Run the "DELETE reserve" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_reserve_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct DeleteReserveState *drs = cls;
+  const struct TALER_TESTING_Command *reserve_cmd;
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  reserve_cmd = TALER_TESTING_interpreter_lookup_command (
+    is,
+    drs->reserve_reference);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
+                                           0,
+                                           &reserve_pub))
+    TALER_TESTING_FAIL (is);
+
+  drs->is = is;
+  if (drs->purge)
+    drs->rdh = TALER_MERCHANT_reserve_purge (is->ctx,
+                                             drs->merchant_url,
+                                             reserve_pub,
+                                             &delete_reserve_cb,
+                                             drs);
+  else
+    drs->rdh = TALER_MERCHANT_reserve_delete (is->ctx,
+                                              drs->merchant_url,
+                                              reserve_pub,
+                                              &delete_reserve_cb,
+                                              drs);
+
+  GNUNET_assert (NULL != drs->rdh);
+}
+
+
+/**
+ * Free the state of a "DELETE reserve" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_reserve_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct DeleteReserveState *drs = cls;
+
+  if (NULL != drs->rdh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "DELETE /reserves/$RESERVE_PUB operation did not complete\n");
+    TALER_MERCHANT_reserve_delete_cancel (drs->rdh);
+  }
+  GNUNET_free (drs);
+}
+
+
+/**
+ * Define a "DELETE reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /reserves/$RESERVE_PUB request.
+ * @param reserve_reference command label of a command providing a reserve
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_reserve (const char *label,
+                                           const char *merchant_url,
+                                           const char *reserve_reference,
+                                           unsigned int http_status)
+{
+  struct DeleteReserveState *drs;
+
+  drs = GNUNET_new (struct DeleteReserveState);
+  drs->merchant_url = merchant_url;
+  drs->reserve_reference = reserve_reference;
+  drs->http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = drs,
+      .label = label,
+      .run = &delete_reserve_run,
+      .cleanup = &delete_reserve_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Define a "PURGE reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        DELETE /reserves/$RESERVE_PUB request.
+ * @param reserve_reference command label of a command providing a reserve
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_purge_reserve (const char *label,
+                                          const char *merchant_url,
+                                          const char *reserve_reference,
+                                          unsigned int http_status)
+{
+  struct DeleteReserveState *drs;
+
+  drs = GNUNET_new (struct DeleteReserveState);
+  drs->merchant_url = merchant_url;
+  drs->reserve_reference = reserve_reference;
+  drs->http_status = http_status;
+  drs->purge = true;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = drs,
+      .label = label,
+      .run = &delete_reserve_run,
+      .cleanup = &delete_reserve_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_delete_reserve.c */
diff --git a/src/testing/testing_api_cmd_get_instance.c 
b/src/testing/testing_api_cmd_get_instance.c
new file mode 100644
index 0000000..a3b9113
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_instance.c
@@ -0,0 +1,377 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_instance.c
+ * @brief command to test GET /instance/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET instance" CMD.
+ */
+struct GetInstanceState
+{
+
+  /**
+   * Handle for a "GET instance" request.
+   */
+  struct TALER_MERCHANT_InstanceGetHandle *igh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the instance to run GET for.
+   */
+  const char *instance_id;
+
+  /**
+   * Reference for a POST or PATCH /instances CMD (optional).
+   */
+  const char *instance_reference;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /get/instance/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+get_instance_cb (void *cls,
+                 const struct TALER_MERCHANT_HttpResponse *hr,
+                 unsigned int accounts_length,
+                 const struct TALER_MERCHANT_Account accounts[],
+                 const struct TALER_MERCHANT_InstanceDetails *details)
+{
+  /* FIXME, deeper checks should be implemented here (for accounts). */
+  struct GetInstanceState *gis = cls;
+  const struct TALER_TESTING_Command *instance_cmd;
+
+  instance_cmd = TALER_TESTING_interpreter_lookup_command (
+    gis->is,
+    gis->instance_reference);
+
+  gis->igh = NULL;
+  if (gis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gis->is));
+    TALER_TESTING_interpreter_fail (gis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      const char *name;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_string (instance_cmd,
+                                          0,
+                                          &name))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (0 != strcmp (details->name,
+                       name))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance name does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct json_t *address;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_json (instance_cmd,
+                                        0,
+                                        &address))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (1 != json_equal (details->address,
+                           address))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance address does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct json_t *jurisdiction;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_json (instance_cmd,
+                                        1,
+                                        &jurisdiction))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (1 != json_equal (details->jurisdiction,
+                           jurisdiction))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance jurisdiction does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct TALER_Amount *default_max_wire_fee;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_amount_obj (instance_cmd,
+                                              0,
+                                              &default_max_wire_fee))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if ((GNUNET_OK != TALER_amount_cmp_currency (
+             details->default_max_wire_fee,
+             default_max_wire_fee)) ||
+          (0 != TALER_amount_cmp (details->default_max_wire_fee,
+                                  default_max_wire_fee)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance default max wire fee does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const uint32_t *default_wire_fee_amortization;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_uint32 (instance_cmd,
+                                          0,
+                                          &default_wire_fee_amortization))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (details->default_wire_fee_amortization !=
+          *default_wire_fee_amortization)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance default wire fee amortization does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct TALER_Amount *default_max_deposit_fee;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_amount_obj (instance_cmd,
+                                              0,
+                                              &default_max_deposit_fee))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if ((GNUNET_OK != TALER_amount_cmp_currency (
+             details->default_max_deposit_fee,
+             default_max_deposit_fee)) ||
+          (0 != TALER_amount_cmp (details->default_max_deposit_fee,
+                                  default_max_deposit_fee)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance default max deposit fee does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct GNUNET_TIME_Relative *default_wire_transfer_delay;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_relative_time (instance_cmd,
+                                                 0,
+                                                 &default_wire_transfer_delay))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (details->default_wire_transfer_delay.rel_value_us !=
+          default_wire_transfer_delay->rel_value_us)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance default wire transfer delay does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct GNUNET_TIME_Relative *default_pay_delay;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_relative_time (instance_cmd,
+                                                 1,
+                                                 &default_pay_delay))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (details->default_pay_delay.rel_value_us !=
+          default_pay_delay->rel_value_us)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Instance default pay delay does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const unsigned int *expected_accounts_length;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_uint32 (instance_cmd,
+                                          1,
+                                          &expected_accounts_length))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Could not fetch accounts length\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+      if (accounts_length != *expected_accounts_length)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Accounts length does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    for (unsigned int i = 0; i < accounts_length; ++i)
+    {
+      const char *payto_uri;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_string (instance_cmd,
+                                          2 + i,
+                                          &payto_uri))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Could not fetch account payto uri\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+      if (0 != strcasecmp (accounts[i].payto_uri,
+                           payto_uri))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Account payto uri does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+      // FIXME: account for deactivated accounts
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET instance" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_instance_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct GetInstanceState *gis = cls;
+
+  gis->is = is;
+  gis->igh = TALER_MERCHANT_instance_get (is->ctx,
+                                          gis->merchant_url,
+                                          gis->instance_id,
+                                          &get_instance_cb,
+                                          gis);
+  GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET instance" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_instance_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct GetInstanceState *gis = cls;
+
+  if (NULL != gis->igh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET /instances/$ID operation did not complete\n");
+    TALER_MERCHANT_instance_get_cancel (gis->igh);
+  }
+  GNUNET_free (gis);
+}
+
+
+/**
+ * Define a "GET instance" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /instances/$ID request.
+ * @param instance_id the ID of the instance to query
+ * @param http_status expected HTTP response code.
+ * @param instance_reference reference to a "POST /instances" or "PATCH 
/instances/$ID" CMD
+ *        that will provide what we expect the backend to return to us
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_instance (const char *label,
+                                         const char *merchant_url,
+                                         const char *instance_id,
+                                         unsigned int http_status,
+                                         const char *instance_reference)
+{
+  struct GetInstanceState *gis;
+
+  gis = GNUNET_new (struct GetInstanceState);
+  gis->merchant_url = merchant_url;
+  gis->instance_id = instance_id;
+  gis->http_status = http_status;
+  gis->instance_reference = instance_reference;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gis,
+      .label = label,
+      .run = &get_instance_run,
+      .cleanup = &get_instance_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_instance.c */
diff --git a/src/testing/testing_api_cmd_get_instances.c 
b/src/testing/testing_api_cmd_get_instances.c
new file mode 100644
index 0000000..5a7c55c
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_instances.c
@@ -0,0 +1,266 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_instances.c
+ * @brief command to test GET /instances
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET instances" CMD.
+ */
+struct GetInstancesState
+{
+
+  /**
+   * Handle for a "GET instance" request.
+   */
+  struct TALER_MERCHANT_InstancesGetHandle *igh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * The list of instance references to compare to.
+   */
+  const char **instances;
+
+  /**
+   * The length of @e instances.
+   */
+  unsigned int instances_length;
+
+};
+
+
+/**
+ * Callback for a GET /instances operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+get_instances_cb (void *cls,
+                  const struct TALER_MERCHANT_HttpResponse *hr,
+                  unsigned int iis_length,
+                  const struct TALER_MERCHANT_InstanceInformation iis[])
+{
+  struct GetInstancesState *gis = cls;
+
+  gis->igh = NULL;
+  if (gis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gis->is));
+    TALER_TESTING_interpreter_fail (gis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    if (iis_length != gis->instances_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Length of instances found does not match\n");
+      TALER_TESTING_interpreter_fail (gis->is);
+      return;
+    }
+    for (unsigned int i = 0; i < iis_length; ++i)
+    {
+      const struct TALER_TESTING_Command *instance_cmd;
+
+      instance_cmd = TALER_TESTING_interpreter_lookup_command (
+        gis->is,
+        gis->instances[i]);
+
+      {
+        const char *name;
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_string (instance_cmd,
+                                            0,
+                                            &name))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch instance name\n");
+          TALER_TESTING_interpreter_fail (gis->is);
+          return;
+        }
+        if (0 != strcmp (iis[i].name,
+                         name))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Instance name does not match\n");
+          TALER_TESTING_interpreter_fail (gis->is);
+          return;
+        }
+      }
+
+      {
+        const char *id;
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_string (instance_cmd,
+                                            1,
+                                            &id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch instance id\n");
+          TALER_TESTING_interpreter_fail (gis->is);
+          return;
+        }
+        if (0 != strcmp (iis[i].id,
+                         id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Instance id does not match\n");
+          TALER_TESTING_interpreter_fail (gis->is);
+          return;
+        }
+      }
+    }
+
+    // FIXME: compare payment_targets
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET /instances" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_instances_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct GetInstancesState *gis = cls;
+
+  gis->is = is;
+  gis->igh = TALER_MERCHANT_instances_get (is->ctx,
+                                           gis->merchant_url,
+                                           &get_instances_cb,
+                                           gis);
+  GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET instance" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_instances_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct GetInstancesState *gis = cls;
+
+  if (NULL != gis->igh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET /instances operation did not complete\n");
+    TALER_MERCHANT_instances_get_cancel (gis->igh);
+  }
+  GNUNET_array_grow (gis->instances,
+                     gis->instances_length,
+                     0);
+  GNUNET_free (gis);
+}
+
+
+/**
+ * Define a "GET /instances" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /instances request.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        product (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_instances (const char *label,
+                                          const char *merchant_url,
+                                          unsigned int http_status,
+                                          ...)
+{
+  struct GetInstancesState *gis;
+
+  gis = GNUNET_new (struct GetInstancesState);
+  gis->merchant_url = merchant_url;
+  gis->http_status = http_status;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (gis->instances,
+                           gis->instances_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gis,
+      .label = label,
+      .run = &get_instances_run,
+      .cleanup = &get_instances_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_instances.c */
diff --git a/src/testing/testing_api_cmd_get_orders.c 
b/src/testing/testing_api_cmd_get_orders.c
new file mode 100644
index 0000000..db3e20a
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_orders.c
@@ -0,0 +1,576 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_orders.c
+ * @brief command to test GET /orders
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET orders" CMD.
+ */
+struct GetOrdersState
+{
+
+  /**
+   * Handle for a "GET orders" request.
+   */
+  struct TALER_MERCHANT_OrdersGetHandle *ogh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * A NULL-terminated array of CMD labels that created orders.
+   */
+  const char **orders;
+
+  /**
+   * The length of @e orders.
+   */
+  unsigned int orders_length;
+
+};
+
+
+/**
+ * Callback for a GET /orders operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+get_orders_cb (void *cls,
+               const struct TALER_MERCHANT_HttpResponse *hr,
+               unsigned int orders_length,
+               const struct TALER_MERCHANT_OrderEntry orders[])
+{
+  struct GetOrdersState *gos = cls;
+
+  gos->ogh = NULL;
+  if (gos->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gos->is));
+    TALER_TESTING_interpreter_fail (gos->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    if (orders_length != gos->orders_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Number of orders found does not match\n");
+      TALER_TESTING_interpreter_fail (gos->is);
+      return;
+    }
+    for (unsigned int i = 0; i < orders_length; ++i)
+    {
+      const struct TALER_TESTING_Command *order_cmd;
+
+      order_cmd = TALER_TESTING_interpreter_lookup_command (
+        gos->is,
+        gos->orders[i]);
+
+      {
+        const char *order_id;
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_order_id (order_cmd,
+                                              0,
+                                              &order_id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch order id\n");
+          TALER_TESTING_interpreter_fail (gos->is);
+          return;
+        }
+        if (0 != strcmp (orders[i].order_id,
+                         order_id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Order id does not match\n");
+          TALER_TESTING_interpreter_fail (gos->is);
+          return;
+        }
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gos->is);
+}
+
+
+/**
+ * Run the "GET /orders" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_orders_run (void *cls,
+                const struct TALER_TESTING_Command *cmd,
+                struct TALER_TESTING_Interpreter *is)
+{
+  struct GetOrdersState *gos = cls;
+
+  gos->is = is;
+  gos->ogh = TALER_MERCHANT_orders_get (is->ctx,
+                                        gos->merchant_url,
+                                        &get_orders_cb,
+                                        gos);
+  GNUNET_assert (NULL != gos->ogh);
+}
+
+
+/**
+ * Free the state of a "GET orders" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_orders_cleanup (void *cls,
+                    const struct TALER_TESTING_Command *cmd)
+{
+  struct GetOrdersState *gos = cls;
+
+  if (NULL != gos->ogh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET /orders operation did not complete\n");
+    TALER_MERCHANT_orders_get_cancel (gos->ogh);
+  }
+  GNUNET_free (gos);
+}
+
+
+/**
+ * Define a "GET /orders" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /orders request.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        reserve (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_orders (const char *label,
+                                       const char *merchant_url,
+                                       unsigned int http_status,
+                                       ...)
+{
+  struct GetOrdersState *gos;
+
+  gos = GNUNET_new (struct GetOrdersState);
+  gos->merchant_url = merchant_url;
+  gos->http_status = http_status;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (gos->orders,
+                           gos->orders_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gos,
+      .label = label,
+      .run = &get_orders_run,
+      .cleanup = &get_orders_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+struct MerchantPollOrdersConcludeState
+{
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reference to a command that can provide a poll orders start command.
+   */
+  const char *start_reference;
+
+  /**
+   * Task to wait for the deadline.
+   */
+  struct GNUNET_SCHEDULER_Task *task;
+
+  /**
+   * Expected HTTP response status code.
+   */
+  unsigned int expected_http_status;
+};
+
+
+struct MerchantPollOrdersStartState
+{
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * The handle to the current GET /private/orders request.
+   */
+  struct TALER_MERCHANT_OrdersGetHandle *ogh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * How long to wait for server to return a response.
+   */
+  struct GNUNET_TIME_Relative timeout;
+
+  /**
+   * Conclude state waiting for completion (if any).
+   */
+  struct MerchantPollOrdersConcludeState *cs;
+
+  /**
+   * The HTTP status code returned by the backend.
+   */
+  unsigned int http_status;
+
+  /**
+   * When the request should be completed by.
+   */
+  struct GNUNET_TIME_Absolute deadline;
+};
+
+
+/**
+ * Task called when either the timeout for the get orders
+ * command expired or we got a response.  Checks if the
+ * result is what we expected.
+ *
+ * @param cls a `struct MerchantPollOrdersConcludeState`
+ */
+static void
+conclude_task (void *cls)
+{
+  struct MerchantPollOrdersConcludeState *poc = cls;
+  const struct TALER_TESTING_Command *poll_cmd;
+  struct MerchantPollOrdersStartState *pos;
+  struct GNUNET_TIME_Absolute now;
+
+  poc->task = NULL;
+  poll_cmd =
+    TALER_TESTING_interpreter_lookup_command (poc->is,
+                                              poc->start_reference);
+  if (NULL == poll_cmd)
+    TALER_TESTING_FAIL (poc->is);
+  pos = poll_cmd->cls;
+  if (NULL != pos->ogh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected poll GET /private/orders to have completed, but it 
did not!\n");
+    TALER_TESTING_FAIL (poc->is);
+  }
+  if (pos->http_status != poc->expected_http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected HTTP status %u, got %u\n",
+                poc->expected_http_status,
+                pos->http_status);
+    TALER_TESTING_FAIL (poc->is);
+  }
+  now = GNUNET_TIME_absolute_get ();
+  if ((GNUNET_TIME_absolute_add (pos->deadline,
+                                 GNUNET_TIME_UNIT_SECONDS).abs_value_us <
+       now.abs_value_us) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected answer to be delayed until %llu, but got response at 
%llu\n",
+                (unsigned long long) pos->deadline.abs_value_us,
+                (unsigned long long) now.abs_value_us);
+    TALER_TESTING_FAIL (poc->is);
+  }
+  TALER_TESTING_interpreter_next (poc->is);
+}
+
+
+/**
+ * Callback to process a GET /orders request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param osr order status response details (on success)
+ */
+static void
+merchant_poll_orders_cb (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  unsigned int orders_length,
+  const struct TALER_MERCHANT_OrderEntry orders[])
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct MerchantPollOrdersStartState *pos = cls;
+
+  pos->ogh = NULL;
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pos->is));
+    TALER_TESTING_interpreter_fail (pos->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use order references
+    // check if the data returned matches that from the POST / PATCH
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  pos->http_status = hr->http_status;
+  if (NULL != pos->cs)
+  {
+    GNUNET_SCHEDULER_cancel (pos->cs->task);
+    pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+                                              pos->cs);
+  }
+}
+
+
+/**
+ * Run the "GET orders" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+merchant_poll_orders_start_run (void *cls,
+                                const struct TALER_TESTING_Command *cmd,
+                                struct TALER_TESTING_Interpreter *is)
+{
+  struct MerchantPollOrdersStartState *pos = cls;
+
+  /* add 1s grace time to timeout */
+  pos->deadline
+    = GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute 
(pos->timeout),
+                                GNUNET_TIME_UNIT_SECONDS);
+  pos->is = is;
+  pos->ogh = TALER_MERCHANT_orders_get2 (is->ctx,
+                                         pos->merchant_url,
+                                         TALER_EXCHANGE_YNA_ALL,
+                                         TALER_EXCHANGE_YNA_ALL,
+                                         TALER_EXCHANGE_YNA_ALL,
+                                         GNUNET_TIME_UNIT_ZERO_ABS,
+                                         1,
+                                         2,
+                                         pos->timeout,
+                                         &merchant_poll_orders_cb,
+                                         pos);
+  GNUNET_assert (NULL != pos->ogh);
+  /* We CONTINUE to run the interpreter while the long-polled command
+     completes asynchronously! */
+  TALER_TESTING_interpreter_next (pos->is);
+}
+
+
+/**
+ * Free the state of a "GET orders" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+merchant_poll_orders_start_cleanup (void *cls,
+                                    const struct TALER_TESTING_Command *cmd)
+{
+  struct MerchantPollOrdersStartState *pos = cls;
+
+  if (NULL != pos->ogh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Command `%s' was not terminated\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  pos->is));
+    TALER_MERCHANT_orders_get_cancel (pos->ogh);
+  }
+  GNUNET_free (pos);
+}
+
+
+/**
+ * Start a long poll for GET /private/orders.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_orders_start (const char *label,
+                                     const char *merchant_url,
+                                     struct GNUNET_TIME_Relative timeout)
+{
+  struct MerchantPollOrdersStartState *pos;
+
+  pos = GNUNET_new (struct MerchantPollOrdersStartState);
+  pos->merchant_url = merchant_url;
+  pos->timeout = timeout;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pos,
+      .label = label,
+      .run = &merchant_poll_orders_start_run,
+      .cleanup = &merchant_poll_orders_start_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Run the "GET orders" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+merchant_poll_orders_conclude_run (void *cls,
+                                   const struct TALER_TESTING_Command *cmd,
+                                   struct TALER_TESTING_Interpreter *is)
+{
+  struct MerchantPollOrdersConcludeState *poc = cls;
+  const struct TALER_TESTING_Command *poll_cmd;
+  struct MerchantPollOrdersStartState *pos;
+
+  poc->is = is;
+  poll_cmd =
+    TALER_TESTING_interpreter_lookup_command (is,
+                                              poc->start_reference);
+  if (NULL == poll_cmd)
+    TALER_TESTING_FAIL (poc->is);
+  GNUNET_assert (poll_cmd->run == &merchant_poll_orders_start_run);
+  pos = poll_cmd->cls;
+  pos->cs = poc;
+  if (NULL == pos->ogh)
+    poc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+                                          poc);
+  else
+    poc->task = GNUNET_SCHEDULER_add_at (pos->deadline,
+                                         &conclude_task,
+                                         poc);
+}
+
+
+/**
+ * Free the state of a "GET orders" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+merchant_poll_orders_conclude_cleanup (void *cls,
+                                       const struct TALER_TESTING_Command *cmd)
+{
+  struct MerchantPollOrdersConcludeState *poc = cls;
+
+  if (NULL != poc->task)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Command `%s' was not terminated\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  poc->is));
+    GNUNET_SCHEDULER_cancel (poc->task);
+    poc->task = NULL;
+  }
+  GNUNET_free (poc);
+}
+
+
+/**
+ * Complete a long poll for GET /private/orders.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_orders_conclude (const char *label,
+                                        unsigned int http_status,
+                                        const char *poll_start_reference)
+{
+  struct MerchantPollOrdersConcludeState *poc;
+
+  poc = GNUNET_new (struct MerchantPollOrdersConcludeState);
+  poc->start_reference = poll_start_reference;
+  poc->expected_http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = poc,
+      .label = label,
+      .run = &merchant_poll_orders_conclude_run,
+      .cleanup = &merchant_poll_orders_conclude_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_orders.c */
diff --git a/src/testing/testing_api_cmd_get_product.c 
b/src/testing/testing_api_cmd_get_product.c
new file mode 100644
index 0000000..72211c4
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_product.c
@@ -0,0 +1,373 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_product.c
+ * @brief command to test GET /product/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET product" CMD.
+ */
+struct GetProductState
+{
+
+  /**
+   * Handle for a "GET product" request.
+   */
+  struct TALER_MERCHANT_ProductGetHandle *igh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the product to run GET for.
+   */
+  const char *product_id;
+
+  /**
+   * Reference for a POST or PATCH /products CMD (optional).
+   */
+  const char *product_reference;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /get/product/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books),
+ *                does NOT indicate remaining stocks, to get remaining stocks,
+ *                subtract @a total_sold and @a total_lost. Note that this 
still
+ *                does not then say how many of the remaining inventory are 
locked.
+ * @param total_sold in @a units, total number of @a unit of product sold
+ * @param total_lost in @a units, total number of @a unit of product lost from 
inventory
+ * @param location where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ */
+static void
+get_product_cb (void *cls,
+                const struct TALER_MERCHANT_HttpResponse *hr,
+                const char *description,
+                const json_t *description_i18n,
+                const char *unit,
+                const struct TALER_Amount *price,
+                const json_t *image,
+                const json_t *taxes,
+                int64_t total_stock,
+                uint64_t total_sold,
+                uint64_t total_lost,
+                const json_t *location,
+                struct GNUNET_TIME_Absolute next_restock)
+{
+  struct GetProductState *gis = cls;
+  const struct TALER_TESTING_Command *product_cmd;
+
+  product_cmd = TALER_TESTING_interpreter_lookup_command (
+    gis->is,
+    gis->product_reference);
+
+  gis->igh = NULL;
+  if (gis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gis->is));
+    TALER_TESTING_interpreter_fail (gis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      const char *expected_description;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_string (product_cmd,
+                                          0,
+                                          &expected_description))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (0 != strcmp (description,
+                       expected_description))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product description does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const json_t *expected_description_i18n;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_json (product_cmd,
+                                        0,
+                                        &expected_description_i18n))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (1 != json_equal (description_i18n,
+                           expected_description_i18n))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product description i18n does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct TALER_Amount *expected_price;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_amount_obj (product_cmd,
+                                              0,
+                                              &expected_price))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if ((GNUNET_OK != TALER_amount_cmp_currency (price,
+                                                   expected_price)) ||
+          (0 != TALER_amount_cmp (price,
+                                  expected_price)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product price does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const json_t *expected_image;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_json (product_cmd,
+                                        1,
+                                        &expected_image))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (1 != json_equal (image,
+                           expected_image))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product image does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const json_t *expected_taxes;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_json (product_cmd,
+                                        2,
+                                        &expected_taxes))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (1 != json_equal (taxes,
+                           expected_taxes))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product taxes do not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const char *expected_unit;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_string (product_cmd,
+                                          1,
+                                          &expected_unit))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (0 != strcmp (unit,
+                       expected_unit))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product unit does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const json_t *expected_location;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_json (product_cmd,
+                                        3,
+                                        &expected_location))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (1 != json_equal (location,
+                           expected_location))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product location does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const int64_t *expected_total_stock;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_int64 (product_cmd,
+                                         0,
+                                         &expected_total_stock))
+        TALER_TESTING_interpreter_fail (gis->is);
+      if (total_stock != *expected_total_stock)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product total stock does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    {
+      const struct GNUNET_TIME_Absolute *expected_next_restock;
+      struct GNUNET_TIME_Absolute expected_next_restock_round;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_absolute_time (product_cmd,
+                                                 0,
+                                                 &expected_next_restock))
+        TALER_TESTING_interpreter_fail (gis->is);
+      expected_next_restock_round = *expected_next_restock;
+      GNUNET_TIME_round_abs (&expected_next_restock_round);
+      if (next_restock.abs_value_us != 
expected_next_restock_round.abs_value_us)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Product next restock does not match\n");
+        TALER_TESTING_interpreter_fail (gis->is);
+        return;
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET product" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_product_run (void *cls,
+                 const struct TALER_TESTING_Command *cmd,
+                 struct TALER_TESTING_Interpreter *is)
+{
+  struct GetProductState *gis = cls;
+
+  gis->is = is;
+  gis->igh = TALER_MERCHANT_product_get (is->ctx,
+                                         gis->merchant_url,
+                                         gis->product_id,
+                                         &get_product_cb,
+                                         gis);
+  GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_product_cleanup (void *cls,
+                     const struct TALER_TESTING_Command *cmd)
+{
+  struct GetProductState *gis = cls;
+
+  if (NULL != gis->igh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET /products/$ID operation did not complete\n");
+    TALER_MERCHANT_product_get_cancel (gis->igh);
+  }
+  GNUNET_free (gis);
+}
+
+
+/**
+ * Define a "GET product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @param product_reference reference to a "POST /products" or "PATCH 
/products/$ID" CMD
+ *        that will provide what we expect the backend to return to us
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_product (const char *label,
+                                        const char *merchant_url,
+                                        const char *product_id,
+                                        unsigned int http_status,
+                                        const char *product_reference)
+{
+  struct GetProductState *gis;
+
+  gis = GNUNET_new (struct GetProductState);
+  gis->merchant_url = merchant_url;
+  gis->product_id = product_id;
+  gis->http_status = http_status;
+  gis->product_reference = product_reference;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gis,
+      .label = label,
+      .run = &get_product_run,
+      .cleanup = &get_product_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_product.c */
diff --git a/src/testing/testing_api_cmd_get_products.c 
b/src/testing/testing_api_cmd_get_products.c
new file mode 100644
index 0000000..8eb5001
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_products.c
@@ -0,0 +1,245 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_products.c
+ * @brief command to test GET /products
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET products" CMD.
+ */
+struct GetProductsState
+{
+
+  /**
+   * Handle for a "GET product" request.
+   */
+  struct TALER_MERCHANT_ProductsGetHandle *igh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * The list of product references.
+   */
+  const char **products;
+
+  /**
+   * Length of @e products.
+   */
+  unsigned int products_length;
+
+};
+
+
+/**
+ * Callback for a GET /products operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param products_length length of the @a products array
+ * @param products array of products the requested instance offers
+ */
+static void
+get_products_cb (void *cls,
+                 const struct TALER_MERCHANT_HttpResponse *hr,
+                 unsigned int products_length,
+                 const struct TALER_MERCHANT_InventoryEntry products[])
+{
+  struct GetProductsState *gis = cls;
+
+  gis->igh = NULL;
+  if (gis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gis->is));
+    TALER_TESTING_interpreter_fail (gis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    if (products_length != gis->products_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Length of products found does not match\n");
+      TALER_TESTING_interpreter_fail (gis->is);
+      return;
+    }
+    for (unsigned int i = 0; i < gis->products_length; ++i)
+    {
+      const struct TALER_TESTING_Command *product_cmd;
+
+      product_cmd = TALER_TESTING_interpreter_lookup_command (
+        gis->is,
+        gis->products[i]);
+
+      {
+        const char *product_id;
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_string (product_cmd,
+                                            2,
+                                            &product_id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch product id\n");
+          TALER_TESTING_interpreter_fail (gis->is);
+          return;
+        }
+        if (0 != strcmp (products[i].product_id,
+                         product_id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Product id does not match\n");
+          TALER_TESTING_interpreter_fail (gis->is);
+          return;
+        }
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET /products" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_products_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct GetProductsState *gis = cls;
+
+  gis->is = is;
+  gis->igh = TALER_MERCHANT_products_get (is->ctx,
+                                          gis->merchant_url,
+                                          &get_products_cb,
+                                          gis);
+  GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_products_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct GetProductsState *gis = cls;
+
+  if (NULL != gis->igh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET /products operation did not complete\n");
+    TALER_MERCHANT_products_get_cancel (gis->igh);
+  }
+  GNUNET_array_grow (gis->products,
+                     gis->products_length,
+                     0);
+  GNUNET_free (gis);
+}
+
+
+/**
+ * Define a "GET /products" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        GET /products request.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        product (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_products (const char *label,
+                                         const char *merchant_url,
+                                         unsigned int http_status,
+                                         ...)
+{
+  struct GetProductsState *gis;
+
+  gis = GNUNET_new (struct GetProductsState);
+  gis->merchant_url = merchant_url;
+  gis->http_status = http_status;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (gis->products,
+                           gis->products_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gis,
+      .label = label,
+      .run = &get_products_run,
+      .cleanup = &get_products_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_products.c */
diff --git a/src/testing/testing_api_cmd_get_reserve.c 
b/src/testing/testing_api_cmd_get_reserve.c
new file mode 100644
index 0000000..06ebb47
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_reserve.c
@@ -0,0 +1,354 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_reserve.c
+ * @brief command to test GET /private/reserves/$RESERVE_PUB
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+struct GetReserveState
+{
+
+  /**
+   * Handle for a "GET reserve" request.
+   */
+  struct TALER_MERCHANT_ReserveGetHandle *rgh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * Label for a command that created a reserve.
+   */
+  const char *reserve_reference;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Fetch tips
+   */
+  bool fetch_tips;
+
+  /**
+   * Length of @e tips.
+   */
+  unsigned int tips_length;
+
+  /**
+   * The list of references to tips.
+   */
+  const char **tips;
+};
+
+
+static void
+get_reserve_cb (void *cls,
+                const struct TALER_MERCHANT_HttpResponse *hr,
+                const struct TALER_MERCHANT_ReserveSummary *rs,
+                bool active,
+                unsigned int tips_length,
+                const struct TALER_MERCHANT_TipDetails tips[])
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct GetReserveState *grs = cls;
+  const struct TALER_TESTING_Command *reserve_cmd;
+
+  reserve_cmd = TALER_TESTING_interpreter_lookup_command (
+    grs->is,
+    grs->reserve_reference);
+
+  grs->rgh = NULL;
+  if (grs->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (grs->is));
+    TALER_TESTING_interpreter_fail (grs->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use grs->reserve_reference here to
+    // check if the data returned matches that from the POST / PATCH
+    {
+      const struct TALER_Amount *initial_amount;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_amount_obj (reserve_cmd,
+                                              0,
+                                              &initial_amount))
+        TALER_TESTING_interpreter_fail (grs->is);
+      if ((GNUNET_OK != TALER_amount_cmp_currency 
(&rs->merchant_initial_amount,
+                                                   initial_amount)) ||
+          (0 != TALER_amount_cmp (&rs->merchant_initial_amount,
+                                  initial_amount)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Reserve initial amount does not match\n");
+        TALER_TESTING_interpreter_fail (grs->is);
+        return;
+      }
+    }
+    if (tips_length != grs->tips_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Number of tips authorized does not match\n");
+      TALER_TESTING_interpreter_fail (grs->is);
+      return;
+    }
+    for (unsigned int i = 0; i < tips_length; ++i)
+    {
+      const struct TALER_TESTING_Command *tip_cmd;
+
+      tip_cmd = TALER_TESTING_interpreter_lookup_command (grs->is,
+                                                          grs->tips[i]);
+      {
+        const struct GNUNET_HashCode *tip_id;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_tip_id (tip_cmd,
+                                            0,
+                                            &tip_id))
+          TALER_TESTING_interpreter_fail (grs->is);
+
+        if (0 != GNUNET_memcmp (&tips[i].tip_id,
+                                tip_id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Reserve tip id does not match\n");
+          TALER_TESTING_interpreter_fail (grs->is);
+          return;
+        }
+      }
+      {
+        const struct TALER_Amount *total_amount;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_amount_obj (tip_cmd,
+                                                0,
+                                                &total_amount))
+          TALER_TESTING_interpreter_fail (grs->is);
+
+        if ((GNUNET_OK != TALER_amount_cmp_currency (&tips[i].amount,
+                                                     total_amount)) ||
+            (0 != TALER_amount_cmp (&tips[i].amount,
+                                    total_amount)))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Reserve tip amount does not match\n");
+          TALER_TESTING_interpreter_fail (grs->is);
+          return;
+        }
+      }
+      {
+        const char *reason;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_string (tip_cmd,
+                                            0,
+                                            &reason))
+          TALER_TESTING_interpreter_fail (grs->is);
+
+        if (0 != strcmp (tips[i].reason,
+                         reason))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Reserve tip reason does not match\n");
+          TALER_TESTING_interpreter_fail (grs->is);
+          return;
+        }
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (grs->is);
+}
+
+
+/**
+ * Run the "GET /private/reserves/$RESERVE_PUB" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_reserve_run (void *cls,
+                 const struct TALER_TESTING_Command *cmd,
+                 struct TALER_TESTING_Interpreter *is)
+{
+  struct GetReserveState *grs = cls;
+  const struct TALER_TESTING_Command *reserve_cmd;
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  reserve_cmd = TALER_TESTING_interpreter_lookup_command (
+    is,
+    grs->reserve_reference);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
+                                           0,
+                                           &reserve_pub))
+    TALER_TESTING_FAIL (is);
+
+  grs->is = is;
+  grs->rgh = TALER_MERCHANT_reserve_get (is->ctx,
+                                         grs->merchant_url,
+                                         reserve_pub,
+                                         grs->fetch_tips,
+                                         &get_reserve_cb,
+                                         grs);
+
+  GNUNET_assert (NULL != grs->rgh);
+}
+
+
+/**
+ * Free the state of a "GET reserve" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_reserve_cleanup (void *cls,
+                     const struct TALER_TESTING_Command *cmd)
+{
+  struct GetReserveState *grs = cls;
+
+  if (NULL != grs->rgh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "GET /private/reserve/$RESERVE_PUB operation did not 
complete\n");
+    TALER_MERCHANT_reserve_get_cancel (grs->rgh);
+  }
+  GNUNET_free (grs);
+}
+
+
+/**
+ * Define a "GET reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the request.
+ * @param http_status expected HTTP response code.
+ * @param reserve_reference reference to a "POST /reserves" that provides the
+ *        information we are expecting.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_reserve (const char *label,
+                                        const char *merchant_url,
+                                        unsigned int http_status,
+                                        const char *reserve_reference)
+{
+  struct GetReserveState *grs;
+
+  grs = GNUNET_new (struct GetReserveState);
+  grs->merchant_url = merchant_url;
+  grs->http_status = http_status;
+  grs->reserve_reference = reserve_reference;
+  grs->fetch_tips = false;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = grs,
+      .label = label,
+      .run = &get_reserve_run,
+      .cleanup = &get_reserve_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Define a "GET reserve" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the request.
+ * @param http_status expected HTTP response code.
+ * @param reserve_reference reference to a "POST /reserves" that provides the
+ *        information we are expecting.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        tip (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_reserve_with_tips (const char *label,
+                                                  const char *merchant_url,
+                                                  unsigned int http_status,
+                                                  const char 
*reserve_reference,
+                                                  ...)
+{
+  struct GetReserveState *grs;
+
+  grs = GNUNET_new (struct GetReserveState);
+  grs->merchant_url = merchant_url;
+  grs->http_status = http_status;
+  grs->reserve_reference = reserve_reference;
+  grs->fetch_tips = true;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, reserve_reference);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (grs->tips,
+                           grs->tips_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = grs,
+      .label = label,
+      .run = &get_reserve_run,
+      .cleanup = &get_reserve_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_reserve.c */
diff --git a/src/testing/testing_api_cmd_get_reserves.c 
b/src/testing/testing_api_cmd_get_reserves.c
new file mode 100644
index 0000000..2c543b6
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_reserves.c
@@ -0,0 +1,264 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_reserves.c
+ * @brief command to test GET /private/reserves
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET reserves" CMD
+ */
+struct GetReservesState
+{
+
+  /**
+   * Handle for a "GET reserves" request.
+   */
+  struct TALER_MERCHANT_ReservesGetHandle *rgh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * A list of reserves to compare with.
+   */
+  const char **reserves;
+
+  /**
+   * Length of @e reserve_refs.
+   */
+  unsigned int reserves_length;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+};
+
+
+static void
+get_reserves_cb (void *cls,
+                 const struct TALER_MERCHANT_HttpResponse *hr,
+                 unsigned int reserves_length,
+                 const struct TALER_MERCHANT_ReserveSummary reserves[])
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct GetReservesState *grs = cls;
+
+  grs->rgh = NULL;
+  if (grs->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (grs->is));
+    TALER_TESTING_interpreter_fail (grs->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    if (reserves_length != grs->reserves_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Length of reserves found does not match\n");
+      TALER_TESTING_interpreter_fail (grs->is);
+      return;
+    }
+    // FIXME: check if the data returned matches that from the POST / PATCH
+    for (unsigned int i = 0; i < reserves_length; ++i)
+    {
+      const struct TALER_TESTING_Command *reserve_cmd;
+
+      reserve_cmd = TALER_TESTING_interpreter_lookup_command (
+        grs->is,
+        grs->reserves[i]);
+      {
+        const struct TALER_ReservePublicKeyP *reserve_pub;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
+                                                 0,
+                                                 &reserve_pub))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch reserve public key\n");
+          TALER_TESTING_interpreter_fail (grs->is);
+          return;
+        }
+        if (0 != GNUNET_memcmp (&reserves[i].reserve_pub,
+                                reserve_pub))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Reserve public key does not match\n");
+          TALER_TESTING_interpreter_fail (grs->is);
+          return;
+        }
+      }
+      {
+        const struct TALER_Amount *initial;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_amount_obj (reserve_cmd,
+                                                0,
+                                                &initial))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch reserve initial balance\n");
+          TALER_TESTING_interpreter_fail (grs->is);
+          return;
+        }
+        if ((GNUNET_OK != TALER_amount_cmp_currency (
+               &reserves[i].merchant_initial_amount,
+               initial)) ||
+            (0 != TALER_amount_cmp (&reserves[i].merchant_initial_amount,
+                                    initial)))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Reserve initial amount does not match\n");
+          TALER_TESTING_interpreter_fail (grs->is);
+          return;
+        }
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (grs->is);
+}
+
+
+/**
+ * Run the "GET /private/reserves" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_reserves_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct GetReservesState *grs = cls;
+
+  grs->is = is;
+  grs->rgh = TALER_MERCHANT_reserves_get (is->ctx,
+                                          grs->merchant_url,
+                                          GNUNET_TIME_UNIT_ZERO_ABS,
+                                          TALER_EXCHANGE_YNA_ALL,
+                                          TALER_EXCHANGE_YNA_ALL,
+                                          &get_reserves_cb,
+                                          grs);
+
+  GNUNET_assert (NULL != grs->rgh);
+}
+
+
+/**
+ * Free the state of a "GET reserves" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_reserves_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct GetReservesState *grs = cls;
+
+  if (NULL != grs->rgh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "GET /private/reserves operation did not complete\n");
+    TALER_MERCHANT_reserves_get_cancel (grs->rgh);
+  }
+  GNUNET_array_grow (grs->reserves,
+                     grs->reserves_length,
+                     0);
+  GNUNET_free (grs);
+}
+
+
+/**
+ * Define a "GET /reserves" CMD
+ *
+ * @param label command label.
+ * @param merchant_url url to the merchant.
+ * @param http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        reserve (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_reserves (const char *label,
+                                         const char *merchant_url,
+                                         unsigned int http_status,
+                                         ...)
+{
+  struct GetReservesState *grs;
+
+  grs = GNUNET_new (struct GetReservesState);
+  grs->merchant_url = merchant_url;
+  grs->http_status = http_status;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (grs->reserves,
+                           grs->reserves_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = grs,
+      .label = label,
+      .run = &get_reserves_run,
+      .cleanup = &get_reserves_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_reserves.c */
diff --git a/src/testing/testing_api_cmd_get_tips.c 
b/src/testing/testing_api_cmd_get_tips.c
new file mode 100644
index 0000000..ae7ffb0
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_tips.c
@@ -0,0 +1,268 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_get_tips.c
+ * @brief command to test GET /private/tips
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "GET tips" CMD.
+ */
+struct GetTipsState
+{
+
+  /**
+   * Handle for a "GET tips" request.
+   */
+  struct TALER_MERCHANT_TipsGetHandle *tgh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Length of @e tips.
+   */
+  unsigned int tips_length;
+
+  /**
+   *
+   */
+  const char **tips;
+
+};
+
+/**
+ * Callback for a GET /private/tips operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param tips_length length of the @a tips array
+ * @param tips array of tips
+ */
+static void
+get_tips_cb (void *cls,
+             const struct TALER_MERCHANT_HttpResponse *hr,
+             unsigned int tips_length,
+             const struct TALER_MERCHANT_TipEntry tips[])
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct GetTipsState *gts = cls;
+
+  gts->tgh = NULL;
+  if (gts->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gts->is));
+    TALER_TESTING_interpreter_fail (gts->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use gis->product_reference here to
+    // check if the data returned matches that from the POST / PATCH
+    if (tips_length != gts->tips_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Tips length does not match\n");
+      TALER_TESTING_interpreter_fail (gts->is);
+      return;
+    }
+    for (unsigned int i = 0; i < tips_length; ++i)
+    {
+      const struct TALER_TESTING_Command *tip_cmd;
+
+      tip_cmd = TALER_TESTING_interpreter_lookup_command (
+        gts->is,
+        gts->tips[i]);
+      {
+        const struct GNUNET_HashCode *tip_id;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_tip_id (tip_cmd,
+                                            0,
+                                            &tip_id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch tip id\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+        if (0 != GNUNET_memcmp (tip_id,
+                                &tips[i].tip_id))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Tip id does not match\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+      }
+      {
+        const struct TALER_Amount *tip_amount;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_amount_obj (tip_cmd,
+                                                0,
+                                                &tip_amount))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch tip amount\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+        if ((GNUNET_OK != TALER_amount_cmp_currency (tip_amount,
+                                                     &tips[i].tip_amount)) ||
+            (0 != TALER_amount_cmp (tip_amount,
+                                    &tips[i].tip_amount)))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Tip amount does not match\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gts->is);
+}
+
+
+/**
+ * Run the "GET /private/tips" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_tips_run (void *cls,
+              const struct TALER_TESTING_Command *cmd,
+              struct TALER_TESTING_Interpreter *is)
+{
+  struct GetTipsState *gts = cls;
+
+  gts->is = is;
+  gts->tgh = TALER_MERCHANT_tips_get (is->ctx,
+                                      gts->merchant_url,
+                                      &get_tips_cb,
+                                      gts);
+
+  GNUNET_assert (NULL != gts->tgh);
+}
+
+
+/**
+ * Free the state of a "GET tips" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_tips_cleanup (void *cls,
+                  const struct TALER_TESTING_Command *cmd)
+{
+  struct GetTipsState *gts = cls;
+
+  if (NULL != gts->tgh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET /private/tips operation did not complete\n");
+    TALER_MERCHANT_tips_get_cancel (gts->tgh);
+  }
+  GNUNET_free (gts);
+}
+
+
+/**
+ * Define a get tips CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        server the /tip-query request.
+ * @param http_status expected HTTP response code for the
+ *        /tip-query request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        tip (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_tips (const char *label,
+                            const char *merchant_url,
+                            unsigned int http_status,
+                            ...)
+{
+  struct GetTipsState *gts;
+
+  gts = GNUNET_new (struct GetTipsState);
+  gts->merchant_url = merchant_url;
+  gts->http_status = http_status;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (gts->tips,
+                           gts->tips_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gts,
+      .label = label,
+      .run = &get_tips_run,
+      .cleanup = &get_tips_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_tips.c */
diff --git a/src/testing/testing_api_cmd_get_transfers.c 
b/src/testing/testing_api_cmd_get_transfers.c
new file mode 100644
index 0000000..34889e8
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_transfers.c
@@ -0,0 +1,357 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2020 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 lib/testing_api_cmd_get_transfers.c
+ * @brief command to test GET /transfers.
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a GET transfers CMD.
+ */
+struct GetTransfersState
+{
+
+  /**
+   * Handle for a "get transfer" request.
+   */
+  struct TALER_MERCHANT_GetTransfersHandle *gth;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * payto URI of the merchant to filter by.
+   */
+  const char *payto_uri;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Reference for a "check bank" CMD.  It offers the
+   * WTID to get.
+   */
+  const char *check_bank_reference;
+
+  /**
+   * Array of POST /transfer command labels we expect to see listed.
+   */
+  const char **transfers;
+
+  /**
+   * Length of @e transfers.
+   */
+  unsigned int transfers_length;
+
+};
+
+
+/**
+ * Check the result of our GET /transfers request to a merchant
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param transfers_length length of the @a transfers array
+ * @param transfers array with details about the transfers we received
+ */
+static void
+get_transfers_cb (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  unsigned int transfers_length,
+  const struct TALER_MERCHANT_TransferData transfers[])
+{
+  struct GetTransfersState *gts = cls;
+
+  gts->gth = NULL;
+  if (gts->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gts->is));
+    TALER_TESTING_interpreter_fail (gts->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    if (transfers_length != gts->transfers_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Transfers length does not match\n");
+      TALER_TESTING_interpreter_fail (gts->is);
+      return;
+    }
+    for (unsigned int i = 0; i < transfers_length; ++i)
+    {
+      const struct TALER_TESTING_Command *transfer_cmd;
+
+      transfer_cmd = TALER_TESTING_interpreter_lookup_command (
+        gts->is,
+        gts->transfers[i]);
+      {
+        const struct TALER_WireTransferIdentifierRawP *wtid;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_wtid (transfer_cmd,
+                                          0,
+                                          &wtid))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch wire transfer id\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+        if (0 != GNUNET_memcmp (wtid,
+                                &transfers[i].wtid))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Wire transfer id does not match\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+      }
+      {
+        const char *payto_uri;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_string (transfer_cmd,
+                                            0,
+                                            &payto_uri))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch wire transfer payto uri\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+        if (0 != strcmp (payto_uri,
+                         transfers[i].payto_uri))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Wire transfer payto uri does not match\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+      }
+      {
+        const struct TALER_Amount *credit_amount;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_amount_obj (transfer_cmd,
+                                                0,
+                                                &credit_amount))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch wire transfer credit amount\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+        if ((GNUNET_OK != TALER_amount_cmp_currency (credit_amount,
+                                                     
&transfers[i].credit_amount))
+            ||
+            (0 != TALER_amount_cmp (credit_amount,
+                                    &transfers[i].credit_amount)))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Wire transfer credit amount does not match\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+      }
+      {
+        const char *exchange_url;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_string (transfer_cmd,
+                                            1,
+                                            &exchange_url))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch wire transfer exchange url\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+        if (0 != strcmp (exchange_url,
+                         transfers[i].exchange_url))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Wire transfer exchange url does not match\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+      }
+      {
+        const struct GNUNET_TIME_Absolute *execution_time;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_absolute_time (transfer_cmd,
+                                                   0,
+                                                   &execution_time))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch wire transfer execution time\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+        if (execution_time->abs_value_us !=
+            transfers[i].execution_time.abs_value_us)
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Wire transfer execution time does not match\n");
+          TALER_TESTING_interpreter_fail (gts->is);
+          return;
+        }
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gts->is);
+}
+
+
+/**
+ * Run the "get transfer" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_transfers_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct GetTransfersState *gts = cls;
+
+  gts->is = is;
+  gts->gth = TALER_MERCHANT_transfers_get (is->ctx,
+                                           gts->merchant_url,
+                                           gts->payto_uri,
+                                           GNUNET_TIME_UNIT_FOREVER_ABS,
+                                           GNUNET_TIME_UNIT_ZERO_ABS,
+                                           INT64_MAX,
+                                           0,
+                                           TALER_EXCHANGE_YNA_ALL,
+                                           &get_transfers_cb,
+                                           gts);
+  GNUNET_assert (NULL != gts->gth);
+}
+
+
+/**
+ * Free the state of a "get transfer" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_transfers_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct GetTransfersState *gts = cls;
+
+  if (NULL != gts->gth)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET /transfer operation did not complete\n");
+    TALER_MERCHANT_transfers_get_cancel (gts->gth);
+  }
+  GNUNET_array_grow (gts->transfers,
+                     gts->transfers_length,
+                     0);
+  GNUNET_free (gts);
+}
+
+
+/**
+ * Define a GET /transfers CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the backend serving the
+ *        "refund increase" request.
+ * @param payto_uri payto URI to filter by, NULL for no filter
+ * @param http_code expected HTTP response code
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        transfer (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_transfers (const char *label,
+                                          const char *merchant_url,
+                                          const char *payto_uri,
+                                          unsigned int http_code,
+                                          ...)
+{
+  struct GetTransfersState *gts;
+
+  gts = GNUNET_new (struct GetTransfersState);
+  gts->merchant_url = merchant_url;
+  gts->payto_uri = payto_uri;
+  gts->http_status = http_code;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_code);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (gts->transfers,
+                           gts->transfers_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gts,
+      .label = label,
+      .run = &get_transfers_run,
+      .cleanup = &get_transfers_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_get_transfers.c */
diff --git a/src/testing/testing_api_cmd_lock_product.c 
b/src/testing/testing_api_cmd_lock_product.c
new file mode 100644
index 0000000..58140ff
--- /dev/null
+++ b/src/testing/testing_api_cmd_lock_product.c
@@ -0,0 +1,219 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_lock_product.c
+ * @brief command to test LOCK /product
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /products/$ID" CMD.
+ */
+struct LockProductState
+{
+
+  /**
+   * Handle for a "GET product" request.
+   */
+  struct TALER_MERCHANT_ProductLockHandle *iph;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the product to run GET for.
+   */
+  const char *product_id;
+
+  /**
+   * UUID that identifies the client holding the lock
+   */
+  struct GNUNET_Uuid uuid;
+
+  /**
+   * duration how long should the lock be held
+   */
+  struct GNUNET_TIME_Relative duration;
+
+  /**
+   * how much product should be locked
+   */
+  uint32_t quantity;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /products/$ID/lock operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+lock_product_cb (void *cls,
+                 const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct LockProductState *pis = cls;
+
+  pis->iph = NULL;
+  if (pis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pis->is));
+    TALER_TESTING_interpreter_fail (pis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  // FIXME: add other legitimate states here...
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "LOCK /products/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+lock_product_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct LockProductState *pis = cls;
+
+  pis->is = is;
+  pis->iph = TALER_MERCHANT_product_lock (is->ctx,
+                                          pis->merchant_url,
+                                          pis->product_id,
+                                          &pis->uuid,
+                                          pis->duration,
+                                          pis->quantity,
+                                          &lock_product_cb,
+                                          pis);
+  GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+lock_product_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct LockProductState *pis = cls;
+
+  if (NULL != pis->iph)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "POST /product/$ID/lock operation did not complete\n");
+    TALER_MERCHANT_product_lock_cancel (pis->iph);
+  }
+  GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "LOCK /products/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        LOCK /product request.
+ * @param product_id the ID of the product to query
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant product
+ * @param name name of the merchant product
+ * @param address physical address of the merchant product
+ * @param jurisdiction jurisdiction of the merchant product
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_lock_product (
+  const char *label,
+  const char *merchant_url,
+  const char *product_id,
+  const struct GNUNET_Uuid *uuid,
+  struct GNUNET_TIME_Relative duration,
+  uint32_t quantity,
+  unsigned int http_status)
+{
+  struct LockProductState *pis;
+
+  pis = GNUNET_new (struct LockProductState);
+  pis->merchant_url = merchant_url;
+  pis->product_id = product_id;
+  pis->http_status = http_status;
+  pis->uuid = *uuid;
+  pis->duration = duration;
+  pis->quantity = quantity;
+
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pis,
+      .label = label,
+      .run = &lock_product_run,
+      .cleanup = &lock_product_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_lock_product.c */
diff --git a/src/testing/testing_api_cmd_merchant_get_order.c 
b/src/testing/testing_api_cmd_merchant_get_order.c
new file mode 100644
index 0000000..76bc75b
--- /dev/null
+++ b/src/testing/testing_api_cmd_merchant_get_order.c
@@ -0,0 +1,709 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_merchant_get_order.c
+ * @brief command to test GET /private/orders/$ORDER_ID.
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a GET /private/orders/$ORDER_ID CMD.
+ */
+struct MerchantGetOrderState
+{
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code for this CMD.
+   */
+  unsigned int http_status;
+
+  /**
+   * The handle to the current GET /private/orders/$ORDER_ID request.
+   */
+  struct TALER_MERCHANT_OrderMerchantGetHandle *ogh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reference to a command that created an order.
+   */
+  const char *order_reference;
+
+  /**
+   * Whether the order was paid or not.
+   */
+  bool paid;
+
+  /**
+   * Whether the order was refunded or not.
+   */
+  bool refunded;
+
+  /**
+   * A NULL-terminated list of refunds associated with this order.
+   */
+  const char **refunds;
+
+  /**
+   * The length of @e refunds.
+   */
+  unsigned int refunds_length;
+};
+
+
+/**
+ * Callback to process a GET /orders/$ID request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param osr order status response details (on success)
+ */
+static void
+merchant_get_order_cb (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct TALER_MERCHANT_OrderStatusResponse *osr)
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct MerchantGetOrderState *gos = cls;
+  const struct TALER_TESTING_Command *order_cmd;
+
+  order_cmd = TALER_TESTING_interpreter_lookup_command (
+    gos->is,
+    gos->order_reference);
+
+  gos->ogh = NULL;
+  if (gos->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gos->is));
+    TALER_TESTING_interpreter_fail (gos->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use gts->tip_reference here to
+    // check if the data returned matches that from the POST / PATCH
+    if (gos->paid != osr->paid)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Order paid does not match\n");
+      TALER_TESTING_interpreter_fail (gos->is);
+      return;
+    }
+    if (gos->paid)
+    {
+      const struct TALER_TESTING_Command *order_cmd;
+
+      order_cmd = TALER_TESTING_interpreter_lookup_command (
+        gos->is,
+        gos->order_reference);
+
+      {
+        const json_t *expected_contract_terms;
+
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_contract_terms (order_cmd,
+                                                    0,
+                                                    &expected_contract_terms))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Could not fetch order contract terms\n");
+          TALER_TESTING_interpreter_fail (gos->is);
+          return;
+        }
+        if (1 != json_equal (expected_contract_terms,
+                             osr->details.paid.contract_terms))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Order contract terms do not match\n");
+          TALER_TESTING_interpreter_fail (gos->is);
+          return;
+        }
+      }
+
+      if (gos->refunded != osr->details.paid.refunded)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Order refunded does not match\n");
+        TALER_TESTING_interpreter_fail (gos->is);
+        return;
+      }
+      if (gos->refunds_length != osr->details.paid.refunds_len)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Number of refunds found does not match\n");
+        TALER_TESTING_interpreter_fail (gos->is);
+        return;
+      }
+      for (unsigned int i = 0; i < gos->refunds_length; ++i)
+      {
+        const struct TALER_TESTING_Command *refund_cmd;
+
+        refund_cmd = TALER_TESTING_interpreter_lookup_command (
+          gos->is,
+          gos->refunds[i]);
+        {
+          const char *expected_amount_str;
+          struct TALER_Amount expected_amount;
+          struct TALER_Amount *amount_found =
+            &osr->details.paid.refunds[i].refund_amount;
+
+          if (GNUNET_OK !=
+              TALER_TESTING_get_trait_string (refund_cmd,
+                                              0,
+                                              &expected_amount_str))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Could not fetch refund amount\n");
+            TALER_TESTING_interpreter_fail (gos->is);
+            return;
+          }
+          GNUNET_assert (GNUNET_OK ==
+                         TALER_string_to_amount (expected_amount_str,
+                                                 &expected_amount));
+          if ((GNUNET_OK !=
+               TALER_amount_cmp_currency (&expected_amount,
+                                          amount_found)) ||
+              (0 != TALER_amount_cmp (&expected_amount,
+                                      amount_found)))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Refund amounts do not match\n");
+            TALER_TESTING_interpreter_fail (gos->is);
+            return;
+          }
+        }
+        {
+          const char *expected_reason;
+
+          if (GNUNET_OK !=
+              TALER_TESTING_get_trait_string (refund_cmd,
+                                              1,
+                                              &expected_reason))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Could not fetch reason\n");
+            TALER_TESTING_interpreter_fail (gos->is);
+            return;
+          }
+          if (0 != strcmp (expected_reason,
+                           osr->details.paid.refunds[i].reason))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Refund reason does not match\n");
+            TALER_TESTING_interpreter_fail (gos->is);
+            return;
+          }
+        }
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gos->is);
+}
+
+
+/**
+ * Run the "GET order" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+merchant_get_order_run (void *cls,
+                        const struct TALER_TESTING_Command *cmd,
+                        struct TALER_TESTING_Interpreter *is)
+{
+  struct MerchantGetOrderState *gos = cls;
+  const struct TALER_TESTING_Command *order_cmd;
+  const char *order_id;
+  const struct GNUNET_HashCode *h_contract;
+
+  order_cmd = TALER_TESTING_interpreter_lookup_command (
+    is,
+    gos->order_reference);
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_order_id (order_cmd,
+                                        0,
+                                        &order_id))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_h_contract_terms (order_cmd,
+                                                0,
+                                                &h_contract))
+    TALER_TESTING_FAIL (is);
+
+  gos->is = is;
+  gos->ogh = TALER_MERCHANT_merchant_order_get (is->ctx,
+                                                gos->merchant_url,
+                                                order_id,
+                                                NULL,
+                                                false,
+                                                GNUNET_TIME_UNIT_ZERO,
+                                                &merchant_get_order_cb,
+                                                gos);
+}
+
+
+/**
+ * Free the state of a "GET order" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+merchant_get_order_cleanup (void *cls,
+                            const struct TALER_TESTING_Command *cmd)
+{
+  struct MerchantGetOrderState *gos = cls;
+
+  if (NULL != gos->ogh)
+  {
+    TALER_LOG_WARNING ("Get tip operation did not complete\n");
+    TALER_MERCHANT_merchant_order_get_cancel (gos->ogh);
+  }
+  GNUNET_free (gos);
+}
+
+
+/**
+ * Define a GET /private/orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param paid whether the order has been paid for or not.
+ * @param refunded whether the order has been refunded.
+ * @param http_status expected HTTP response code for the request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        refunds (commands) we expect to be aggregated in the transfer
+ *        (assuming @a http_code is #MHD_HTTP_OK). If @e refunded is false,
+ *        this parameter is ignored.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_order (const char *label,
+                                      const char *merchant_url,
+                                      const char *order_reference,
+                                      bool paid,
+                                      bool refunded,
+                                      unsigned int http_status,
+                                      ...)
+{
+  struct MerchantGetOrderState *gos;
+
+  gos = GNUNET_new (struct MerchantGetOrderState);
+  gos->merchant_url = merchant_url;
+  gos->order_reference = order_reference;
+  gos->paid = paid;
+  gos->refunded = refunded;
+  gos->http_status = http_status;
+  if (refunded)
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (gos->refunds,
+                           gos->refunds_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gos,
+      .label = label,
+      .run = &merchant_get_order_run,
+      .cleanup = &merchant_get_order_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+struct MerchantPollOrderConcludeState
+{
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reference to a command that can provide a poll order start command.
+   */
+  const char *start_reference;
+
+  /**
+   * Task to wait for the deadline.
+   */
+  struct GNUNET_SCHEDULER_Task *task;
+
+  /**
+   * Expected HTTP response status code.
+   */
+  unsigned int expected_http_status;
+};
+
+
+struct MerchantPollOrderStartState
+{
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * The handle to the current GET /private/orders/$ORDER_ID request.
+   */
+  struct TALER_MERCHANT_OrderMerchantGetHandle *ogh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reference to a command that created an order.
+   */
+  const char *order_id;
+
+  /**
+   * How long to wait for server to return a response.
+   */
+  struct GNUNET_TIME_Relative timeout;
+
+  /**
+   * Conclude state waiting for completion (if any).
+   */
+  struct MerchantPollOrderConcludeState *cs;
+
+  /**
+   * The HTTP status code returned by the backend.
+   */
+  unsigned int http_status;
+
+  /**
+   * When the request should be completed by.
+   */
+  struct GNUNET_TIME_Absolute deadline;
+};
+
+
+/**
+ * Task called when either the timeout for the /poll-payment
+ * command expired or we got a response.  Checks if the
+ * result is what we expected.
+ *
+ * @param cls a `struct PollPaymentConcludeState`
+ */
+static void
+conclude_task (void *cls)
+{
+  struct MerchantPollOrderConcludeState *ppc = cls;
+  const struct TALER_TESTING_Command *poll_cmd;
+  struct MerchantPollOrderStartState *cps;
+  struct GNUNET_TIME_Absolute now;
+
+  ppc->task = NULL;
+  poll_cmd =
+    TALER_TESTING_interpreter_lookup_command (ppc->is,
+                                              ppc->start_reference);
+  if (NULL == poll_cmd)
+    TALER_TESTING_FAIL (ppc->is);
+  cps = poll_cmd->cls;
+  if (NULL != cps->ogh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected poll GET /private/orders/$ORDER_ID to have 
completed, but it did not!\n");
+    TALER_TESTING_FAIL (ppc->is);
+  }
+  if (cps->http_status != ppc->expected_http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected HTTP status %u, got %u\n",
+                ppc->expected_http_status,
+                cps->http_status);
+    TALER_TESTING_FAIL (ppc->is);
+  }
+  now = GNUNET_TIME_absolute_get ();
+  if ((GNUNET_TIME_absolute_add (cps->deadline,
+                                 GNUNET_TIME_UNIT_SECONDS).abs_value_us <
+       now.abs_value_us) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected answer to be delayed until %llu, but got response at 
%llu\n",
+                (unsigned long long) cps->deadline.abs_value_us,
+                (unsigned long long) now.abs_value_us);
+    TALER_TESTING_FAIL (ppc->is);
+  }
+  TALER_TESTING_interpreter_next (ppc->is);
+}
+
+
+/**
+ * Callback to process a GET /orders/$ID request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param osr order status response details (on success)
+ */
+static void
+merchant_poll_order_cb (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct TALER_MERCHANT_OrderStatusResponse *osr)
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct MerchantPollOrderStartState *pos = cls;
+
+  pos->ogh = NULL;
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pos->is));
+    TALER_TESTING_interpreter_fail (pos->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use gts->tip_reference here to
+    // check if the data returned matches that from the POST / PATCH
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  pos->http_status = hr->http_status;
+  if (NULL != pos->cs)
+  {
+    GNUNET_SCHEDULER_cancel (pos->cs->task);
+    pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+                                              pos->cs);
+  }
+}
+
+
+/**
+ * Run the "GET order" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+merchant_poll_order_start_run (void *cls,
+                               const struct TALER_TESTING_Command *cmd,
+                               struct TALER_TESTING_Interpreter *is)
+{
+  struct MerchantPollOrderStartState *pos = cls;
+
+  /* add 1s grace time to timeout */
+  pos->deadline
+    = GNUNET_TIME_absolute_add (GNUNET_TIME_relative_to_absolute 
(pos->timeout),
+                                GNUNET_TIME_UNIT_SECONDS);
+  pos->is = is;
+  pos->ogh = TALER_MERCHANT_merchant_order_get (is->ctx,
+                                                pos->merchant_url,
+                                                pos->order_id,
+                                                NULL,
+                                                false,
+                                                pos->timeout,
+                                                &merchant_poll_order_cb,
+                                                pos);
+  GNUNET_assert (NULL != pos->ogh);
+  /* We CONTINUE to run the interpreter while the long-polled command
+     completes asynchronously! */
+  TALER_TESTING_interpreter_next (pos->is);
+}
+
+
+/**
+ * Free the state of a "GET order" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+merchant_poll_order_start_cleanup (void *cls,
+                                   const struct TALER_TESTING_Command *cmd)
+{
+  struct MerchantPollOrderStartState *pos = cls;
+
+  if (NULL != pos->ogh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Command `%s' was not terminated\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  pos->is));
+    TALER_MERCHANT_merchant_order_get_cancel (pos->ogh);
+  }
+  GNUNET_free (pos);
+}
+
+
+/**
+ * Start a long poll for GET /private/orders/$ORDER_ID.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_order_start (const char *label,
+                                    const char *merchant_url,
+                                    const char *order_id,
+                                    struct GNUNET_TIME_Relative timeout)
+{
+  struct MerchantPollOrderStartState *pos;
+
+  pos = GNUNET_new (struct MerchantPollOrderStartState);
+  pos->order_id = order_id;
+  pos->merchant_url = merchant_url;
+  pos->timeout = timeout;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pos,
+      .label = label,
+      .run = &merchant_poll_order_start_run,
+      .cleanup = &merchant_poll_order_start_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Run the "GET order" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+merchant_poll_order_conclude_run (void *cls,
+                                  const struct TALER_TESTING_Command *cmd,
+                                  struct TALER_TESTING_Interpreter *is)
+{
+  struct MerchantPollOrderConcludeState *poc = cls;
+  const struct TALER_TESTING_Command *poll_cmd;
+  struct MerchantPollOrderStartState *pos;
+
+  poc->is = is;
+  poll_cmd =
+    TALER_TESTING_interpreter_lookup_command (is,
+                                              poc->start_reference);
+  if (NULL == poll_cmd)
+    TALER_TESTING_FAIL (poc->is);
+  GNUNET_assert (poll_cmd->run == &merchant_poll_order_start_run);
+  pos = poll_cmd->cls;
+  pos->cs = poc;
+  if (NULL == pos->ogh)
+    poc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
+                                          poc);
+  else
+    poc->task = GNUNET_SCHEDULER_add_at (pos->deadline,
+                                         &conclude_task,
+                                         poc);
+}
+
+
+/**
+ * Free the state of a "GET order" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+merchant_poll_order_conclude_cleanup (void *cls,
+                                      const struct TALER_TESTING_Command *cmd)
+{
+  struct MerchantPollOrderConcludeState *poc = cls;
+
+  if (NULL != poc->task)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Command `%s' was not terminated\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  poc->is));
+    GNUNET_SCHEDULER_cancel (poc->task);
+    poc->task = NULL;
+  }
+  GNUNET_free (poc);
+}
+
+
+/**
+ * Complete a long poll for GET /private/orders/$ORDER_ID.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_poll_order_conclude (const char *label,
+                                       unsigned int http_status,
+                                       const char *poll_start_reference)
+{
+  struct MerchantPollOrderConcludeState *cps;
+
+  cps = GNUNET_new (struct MerchantPollOrderConcludeState);
+  cps->start_reference = poll_start_reference;
+  cps->expected_http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = cps,
+      .label = label,
+      .run = &merchant_poll_order_conclude_run,
+      .cleanup = &merchant_poll_order_conclude_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_merchant_get_order.c */
diff --git a/src/testing/testing_api_cmd_merchant_get_tip.c 
b/src/testing/testing_api_cmd_merchant_get_tip.c
new file mode 100644
index 0000000..6695d3b
--- /dev/null
+++ b/src/testing/testing_api_cmd_merchant_get_tip.c
@@ -0,0 +1,399 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_merchant_get_tip.c
+ * @brief command to test GET /private/tips/$TIP_ID.
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * State for a GET /private/tips/$TIP_ID CMD.
+ */
+struct MerchantTipGetState
+{
+
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code for this CMD.
+   */
+  unsigned int http_status;
+
+  /**
+   * Whether to fetch and compare pickups.
+   */
+  bool fetch_pickups;
+
+  /**
+   * The length of @e pickups.
+   */
+  unsigned int pickups_length;
+
+  /**
+   * The NULL-terminated list of pickup commands associated with the tip.
+   */
+  const char **pickups;
+
+  /**
+   * The handle to the current GET /tips/$TIP_ID request.
+   */
+  struct TALER_MERCHANT_TipMerchantGetHandle *tgh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reference to a command that created a tip.
+   */
+  const char *tip_reference;
+};
+
+
+/**
+ * Callback for a GET /private/tips/$TIP_ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr http response
+ * @param total_authorized the total amount authorized for the tip
+ * @param total_picked_up the total amount of the tip that has been picked up
+ * @param reason why the tip was authorized
+ * @param expiration when the tip will expire
+ * @param reserve_pub public key of the reserve the tip is drawing from
+ * @param pickups_length number of pickups associated with the tip
+ * @param pickups the array of pickups associated with the tip
+ */
+static void
+merchant_get_tip_cb (void *cls,
+                     const struct TALER_MERCHANT_HttpResponse *hr,
+                     const struct TALER_Amount *total_authorized,
+                     const struct TALER_Amount *total_picked_up,
+                     const char *reason,
+                     struct GNUNET_TIME_Absolute expiration,
+                     const struct TALER_ReservePublicKeyP *reserve_pub,
+                     unsigned int pickups_length,
+                     const struct TALER_MERCHANT_PickupDetail pickups[])
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct MerchantTipGetState *gts = cls;
+  const struct TALER_TESTING_Command *authorize_cmd;
+  struct TALER_Amount expected_total_picked_up;
+
+  authorize_cmd = TALER_TESTING_interpreter_lookup_command (gts->is,
+                                                            
gts->tip_reference);
+
+  gts->tgh = NULL;
+  GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (total_picked_up->currency,
+                                                     
&expected_total_picked_up));
+  if (gts->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gts->is));
+    TALER_TESTING_interpreter_fail (gts->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use gts->tip_reference here to
+    // check if the data returned matches that from the POST / PATCH
+    {
+      const struct TALER_Amount *initial_amount;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_amount_obj (authorize_cmd,
+                                              0,
+                                              &initial_amount))
+        TALER_TESTING_FAIL (gts->is);
+      if ((GNUNET_OK != TALER_amount_cmp_currency (total_authorized,
+                                                   initial_amount)) ||
+          (0 != TALER_amount_cmp (total_authorized,
+                                  initial_amount)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Tip authorized amount does not match\n");
+        TALER_TESTING_interpreter_fail (gts->is);
+        return;
+      }
+    }
+    {
+      const char *justification;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_string (authorize_cmd,
+                                          0,
+                                          &justification))
+        TALER_TESTING_FAIL (gts->is);
+      if (0 != strcmp (reason,
+                       justification))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Tip authorized reason does not match\n");
+        TALER_TESTING_interpreter_fail (gts->is);
+        return;
+      }
+    }
+    {
+      const struct GNUNET_TIME_Absolute *tip_expiration;
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_absolute_time (authorize_cmd,
+                                                 0,
+                                                 &tip_expiration))
+        TALER_TESTING_FAIL (gts->is);
+      if (tip_expiration->abs_value_us != expiration.abs_value_us)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Tip authorized expiration does not match\n");
+        TALER_TESTING_interpreter_fail (gts->is);
+        return;
+      }
+    }
+    if (pickups_length != gts->pickups_length)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Length of pickups array does not match\n");
+      TALER_TESTING_interpreter_fail (gts->is);
+      return;
+    }
+    {
+      for (unsigned int i = 0; i < pickups_length; ++i)
+      {
+        const struct TALER_TESTING_Command *pickup_cmd;
+
+        pickup_cmd = TALER_TESTING_interpreter_lookup_command (gts->is,
+                                                               
gts->pickups[i]);
+        {
+          const uint64_t *num_planchets;
+
+          if (GNUNET_OK !=
+              TALER_TESTING_get_trait_uint64 (pickup_cmd,
+                                              0,
+                                              &num_planchets))
+            TALER_TESTING_FAIL (gts->is);
+
+          if (*num_planchets != pickups[i].num_planchets)
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Pickup planchet count does not match\n");
+            TALER_TESTING_interpreter_fail (gts->is);
+            return;
+          }
+        }
+        {
+          const struct TALER_Amount *total;
+
+          if (GNUNET_OK !=
+              TALER_TESTING_get_trait_amount_obj (pickup_cmd,
+                                                  pickups[i].num_planchets,
+                                                  &total))
+            TALER_TESTING_FAIL (gts->is);
+
+          if ((GNUNET_OK != TALER_amount_cmp_currency (total,
+                                                       &pickups[i].
+                                                       requested_amount)) ||
+              (0 != TALER_amount_cmp (total,
+                                      &pickups[i].requested_amount)))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Pickup planchet sum does not match\n");
+            TALER_TESTING_interpreter_fail (gts->is);
+            return;
+          }
+          GNUNET_assert (0 < TALER_amount_add (&expected_total_picked_up,
+                                               &expected_total_picked_up,
+                                               total));
+        }
+      }
+      if ((GNUNET_OK != TALER_amount_cmp_currency (&expected_total_picked_up,
+                                                   total_picked_up)) ||
+          (0 != TALER_amount_cmp (&expected_total_picked_up,
+                                  total_picked_up)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Tip picked up amount does not match\n");
+        TALER_TESTING_interpreter_fail (gts->is);
+        return;
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gts->is);
+}
+
+
+/**
+ * Run the "GET tip" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+merchant_get_tip_run (void *cls,
+                      const struct TALER_TESTING_Command *cmd,
+                      struct TALER_TESTING_Interpreter *is)
+{
+  struct MerchantTipGetState *tgs = cls;
+  const struct TALER_TESTING_Command *tip_cmd;
+  const struct GNUNET_HashCode *tip_id;
+
+  tip_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                      tgs->tip_reference);
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_tip_id (tip_cmd,
+                                      0,
+                                      &tip_id))
+    TALER_TESTING_FAIL (is);
+
+  tgs->is = is;
+  tgs->tgh = TALER_MERCHANT_merchant_tip_get (is->ctx,
+                                              tgs->merchant_url,
+                                              tip_id,
+                                              tgs->fetch_pickups,
+                                              &merchant_get_tip_cb,
+                                              tgs);
+  GNUNET_assert (NULL != tgs->tgh);
+}
+
+
+/**
+* Free the state of a "GET tip" CMD, and possibly
+* cancel a pending operation thereof.
+*
+* @param cls closure.
+* @param cmd command being run.
+*/
+static void
+merchant_get_tip_cleanup (void *cls,
+                          const struct TALER_TESTING_Command *cmd)
+{
+  struct MerchantTipGetState *tgs = cls;
+
+  if (NULL != tgs->tgh)
+  {
+    TALER_LOG_WARNING ("Get tip operation did not complete\n");
+    TALER_MERCHANT_merchant_tip_get_cancel (tgs->tgh);
+  }
+  GNUNET_free (tgs);
+}
+
+
+/**
+ * Define a GET /private/tips/$TIP_IDE CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_tip (const char *label,
+                                    const char *merchant_url,
+                                    const char *tip_reference,
+                                    unsigned int http_status)
+{
+  struct MerchantTipGetState *tgs;
+
+  tgs = GNUNET_new (struct MerchantTipGetState);
+  tgs->merchant_url = merchant_url;
+  tgs->tip_reference = tip_reference;
+  tgs->http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = tgs,
+      .label = label,
+      .run = &merchant_get_tip_run,
+      .cleanup = &merchant_get_tip_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Define a GET /private/tips/$TIP_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param pickup_refs a NULL-terminated list of pickup commands
+ *        associated with the tip.
+ * @param http_status expected HTTP response code for the request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        pickup (commands) we expect to be returned in the list
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_tip_with_pickups (const char *label,
+                                                 const char *merchant_url,
+                                                 const char *tip_reference,
+                                                 unsigned int http_status,
+                                                 ...)
+{
+  struct MerchantTipGetState *tgs;
+
+  tgs = GNUNET_new (struct MerchantTipGetState);
+  tgs->merchant_url = merchant_url;
+  tgs->tip_reference = tip_reference;
+  tgs->fetch_pickups = true;
+  tgs->http_status = http_status;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (tgs->pickups,
+                           tgs->pickups_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = tgs,
+      .label = label,
+      .run = &merchant_get_tip_run,
+      .cleanup = &merchant_get_tip_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_merchant_get_tip.c */
diff --git a/src/testing/testing_api_cmd_patch_instance.c 
b/src/testing/testing_api_cmd_patch_instance.c
new file mode 100644
index 0000000..8720708
--- /dev/null
+++ b/src/testing/testing_api_cmd_patch_instance.c
@@ -0,0 +1,341 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_patch_instance.c
+ * @brief command to test PATCH /instance
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "PATCH /instance" CMD.
+ */
+struct PatchInstanceState
+{
+
+  /**
+   * Handle for a "PATCH /instance/$ID" request.
+   */
+  struct TALER_MERCHANT_InstancePatchHandle *iph;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the instance to run PATCH for.
+   */
+  const char *instance_id;
+
+  /**
+   * Length of the @payto_uris array
+   */
+  unsigned int payto_uris_length;
+
+  /**
+   * Array of payto URIs.
+   */
+  const char **payto_uris;
+
+  /**
+   * Name of the instance.
+   */
+  const char *name;
+
+  /**
+   * Address to use.
+   */
+  json_t *address;
+
+  /**
+   * Jurisdiction to use.
+   */
+  json_t *jurisdiction;
+
+  /**
+   * Wire fee to use.
+   */
+  struct TALER_Amount default_max_wire_fee;
+
+  /**
+   * Amortization to use.
+   */
+  uint32_t default_wire_fee_amortization;
+
+  /**
+   * Deposit fee ceiling to use.
+   */
+  struct TALER_Amount default_max_deposit_fee;
+
+  /**
+   * Wire transfer delay to use.
+   */
+  struct GNUNET_TIME_Relative default_wire_transfer_delay;
+
+  /**
+   * Order validity default duration to use.
+   */
+  struct GNUNET_TIME_Relative default_pay_delay;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a PATCH /instances/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+patch_instance_cb (void *cls,
+                   const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct PatchInstanceState *pis = cls;
+
+  pis->iph = NULL;
+  if (pis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pis->is));
+    TALER_TESTING_interpreter_fail (pis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  // FIXME: add other legitimate states here...
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "PATCH /instances/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+patch_instance_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct PatchInstanceState *pis = cls;
+
+  pis->is = is;
+  pis->iph = TALER_MERCHANT_instance_patch (is->ctx,
+                                            pis->merchant_url,
+                                            pis->instance_id,
+                                            pis->payto_uris_length,
+                                            pis->payto_uris,
+                                            pis->name,
+                                            pis->address,
+                                            pis->jurisdiction,
+                                            &pis->default_max_wire_fee,
+                                            pis->default_wire_fee_amortization,
+                                            &pis->default_max_deposit_fee,
+                                            pis->default_wire_transfer_delay,
+                                            pis->default_pay_delay,
+                                            &patch_instance_cb,
+                                            pis);
+  GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Offers information from the PATCH /instances CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+patch_instance_traits (void *cls,
+                       const void **ret,
+                       const char *trait,
+                       unsigned int index)
+{
+  struct PatchInstanceState *pis = cls;
+  #define NUM_TRAITS (pis->payto_uris_length) + 11
+  struct TALER_TESTING_Trait traits[NUM_TRAITS];
+  traits[0] =
+    TALER_TESTING_make_trait_string (0, pis->name);
+  traits[1] =
+    TALER_TESTING_make_trait_string (1, pis->instance_id);
+  traits[2] =
+    TALER_TESTING_make_trait_json (0, pis->address);
+  traits[3] =
+    TALER_TESTING_make_trait_json (1, pis->jurisdiction);
+  traits[4] =
+    TALER_TESTING_make_trait_amount_obj (0, &pis->default_max_wire_fee);
+  traits[5] =
+    TALER_TESTING_make_trait_uint32 (0, &pis->default_wire_fee_amortization);
+  traits[6] =
+    TALER_TESTING_make_trait_amount_obj (1, &pis->default_max_deposit_fee);
+  traits[7] =
+    TALER_TESTING_make_trait_relative_time (0,
+                                            &pis->default_wire_transfer_delay);
+  traits[8] =
+    TALER_TESTING_make_trait_relative_time (1, &pis->default_pay_delay);
+  traits[9] =
+    TALER_TESTING_make_trait_uint32 (1, &pis->payto_uris_length);
+  traits[NUM_TRAITS - 1] =
+    TALER_TESTING_trait_end ();
+  for (unsigned int i = 0; i < pis->payto_uris_length; ++i)
+  {
+    traits[10 + i] =
+      TALER_TESTING_make_trait_string (2 + i, pis->payto_uris[i]);
+  }
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Free the state of a "PATCH /instances/$ID" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+patch_instance_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct PatchInstanceState *pis = cls;
+
+  if (NULL != pis->iph)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "PATCH /instance/$ID operation did not complete\n");
+    TALER_MERCHANT_instance_patch_cancel (pis->iph);
+  }
+  json_decref (pis->address);
+  json_decref (pis->jurisdiction);
+  GNUNET_free (pis->payto_uris);
+  GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "PATCH /instances/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        PATCH /instance request.
+ * @param instance_id the ID of the instance to query
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_instance (
+  const char *label,
+  const char *merchant_url,
+  const char *instance_id,
+  unsigned int payto_uris_length,
+  const char *payto_uris[],
+  const char *name,
+  json_t *address,
+  json_t *jurisdiction,
+  const char *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const char *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  unsigned int http_status)
+{
+  struct PatchInstanceState *pis;
+
+  pis = GNUNET_new (struct PatchInstanceState);
+  pis->merchant_url = merchant_url;
+  pis->instance_id = instance_id;
+  pis->http_status = http_status;
+  pis->payto_uris_length = payto_uris_length;
+  pis->payto_uris = GNUNET_new_array (payto_uris_length,
+                                      const char *);
+  memcpy (pis->payto_uris,
+          payto_uris,
+          sizeof (const char *) * payto_uris_length);
+  pis->name = name;
+  pis->address = address; /* ownership transfer! */
+  pis->jurisdiction = jurisdiction; /* ownership transfer! */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (default_max_wire_fee,
+                                         &pis->default_max_wire_fee));
+  pis->default_wire_fee_amortization = default_wire_fee_amortization;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (default_max_deposit_fee,
+                                         &pis->default_max_deposit_fee));
+  pis->default_wire_transfer_delay = default_wire_transfer_delay;
+  pis->default_pay_delay = default_pay_delay;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pis,
+      .label = label,
+      .run = &patch_instance_run,
+      .cleanup = &patch_instance_cleanup,
+      .traits = &patch_instance_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_patch_instance.c */
diff --git a/src/testing/testing_api_cmd_patch_product.c 
b/src/testing/testing_api_cmd_patch_product.c
new file mode 100644
index 0000000..a5b5b74
--- /dev/null
+++ b/src/testing/testing_api_cmd_patch_product.c
@@ -0,0 +1,324 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_patch_product.c
+ * @brief command to test PATCH /product
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "PATCH /product" CMD.
+ */
+struct PatchProductState
+{
+
+  /**
+   * Handle for a "GET product" request.
+   */
+  struct TALER_MERCHANT_ProductPatchHandle *iph;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the product to run GET for.
+   */
+  const char *product_id;
+
+  /**
+   * description of the product
+   */
+  const char *description;
+
+  /**
+   * Map from IETF BCP 47 language tags to localized descriptions
+   */
+  json_t *description_i18n;
+
+  /**
+   * unit in which the product is measured (liters, kilograms, packages, etc.)
+   */
+  const char *unit;
+
+  /**
+   * the price for one @a unit of the product
+   */
+  struct TALER_Amount price;
+
+  /**
+   * base64-encoded product image
+   */
+  json_t *image;
+
+  /**
+   * list of taxes paid by the merchant
+   */
+  json_t *taxes;
+
+  /**
+   * in @e units, -1 to indicate "infinite" (i.e. electronic books)
+   */
+  int64_t total_stock;
+
+  /**
+   * in @e units.
+   */
+  int64_t total_lost;
+
+  /**
+   * where the product is in stock
+   */
+  json_t *address;
+
+  /**
+   * when the next restocking is expected to happen, 0 for unknown,
+   */
+  struct GNUNET_TIME_Absolute next_restock;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a PATCH /products/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+patch_product_cb (void *cls,
+                  const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct PatchProductState *pis = cls;
+
+  pis->iph = NULL;
+  if (pis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pis->is));
+    TALER_TESTING_interpreter_fail (pis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  // FIXME: add other legitimate states here...
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "PATCH /products/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+patch_product_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct PatchProductState *pis = cls;
+
+  pis->is = is;
+  pis->iph = TALER_MERCHANT_product_patch (is->ctx,
+                                           pis->merchant_url,
+                                           pis->product_id,
+                                           pis->description,
+                                           pis->description_i18n,
+                                           pis->unit,
+                                           &pis->price,
+                                           pis->image,
+                                           pis->taxes,
+                                           pis->total_stock,
+                                           pis->total_lost,
+                                           pis->address,
+                                           pis->next_restock,
+                                           &patch_product_cb,
+                                           pis);
+  GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Offers information from the PATCH /products CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+patch_product_traits (void *cls,
+                      const void **ret,
+                      const char *trait,
+                      unsigned int index)
+{
+  struct PatchProductState *pps = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_string (0, pps->description),
+    TALER_TESTING_make_trait_json (0, pps->description_i18n),
+    TALER_TESTING_make_trait_string (1, pps->unit),
+    TALER_TESTING_make_trait_amount_obj (0, &pps->price),
+    TALER_TESTING_make_trait_json (1, pps->image),
+    TALER_TESTING_make_trait_json (2, pps->taxes),
+    TALER_TESTING_make_trait_int64 (0, &pps->total_stock),
+    TALER_TESTING_make_trait_json (3, pps->address),
+    TALER_TESTING_make_trait_absolute_time (0, &pps->next_restock),
+    TALER_TESTING_make_trait_string (2, pps->product_id),
+    TALER_TESTING_trait_end (),
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+patch_product_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct PatchProductState *pis = cls;
+
+  if (NULL != pis->iph)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "PATCH /products/$ID operation did not complete\n");
+    TALER_MERCHANT_product_patch_cancel (pis->iph);
+  }
+  json_decref (pis->description_i18n);
+  json_decref (pis->image);
+  json_decref (pis->taxes);
+  json_decref (pis->address);
+  GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "PATCH /products/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        PATCH /product request.
+ * @param product_id the ID of the product to query
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books)
+ * @param total_lost in @a units, must be larger than previous values, and may
+ *               not exceed total_stock minus total_sold; if it does, the 
transaction
+ *               will fail with a #MHD_HTTP_CONFLICT HTTP status code
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_product (
+  const char *label,
+  const char *merchant_url,
+  const char *product_id,
+  const char *description,
+  json_t *description_i18n,
+  const char *unit,
+  const char *price,
+  json_t *image,
+  json_t *taxes,
+  int64_t total_stock,
+  uint64_t total_lost,
+  json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  unsigned int http_status)
+{
+  struct PatchProductState *pis;
+
+  pis = GNUNET_new (struct PatchProductState);
+  pis->merchant_url = merchant_url;
+  pis->product_id = product_id;
+  pis->http_status = http_status;
+  pis->description = description;
+  pis->description_i18n = description_i18n; /* ownership taken */
+  pis->unit = unit;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (price,
+                                         &pis->price));
+  pis->image = image; /* ownership taken */
+  pis->taxes = taxes; /* ownership taken */
+  pis->total_stock = total_stock;
+  pis->total_lost = total_lost;
+  pis->address = address; /* ownership taken */
+  pis->next_restock = next_restock; {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pis,
+      .label = label,
+      .run = &patch_product_run,
+      .cleanup = &patch_product_cleanup,
+      .traits = &patch_product_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_patch_product.c */
diff --git a/src/lib/testing_api_cmd_pay.c 
b/src/testing/testing_api_cmd_pay_order.c
similarity index 53%
rename from src/lib/testing_api_cmd_pay.c
rename to src/testing/testing_api_cmd_pay_order.c
index 02bdf40..c576c31 100644
--- a/src/lib/testing_api_cmd_pay.c
+++ b/src/testing/testing_api_cmd_pay_order.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2018 Taler Systems SA
+  Copyright (C) 2014-2018, 2020 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
@@ -16,13 +16,12 @@
   License along with TALER; see the file COPYING.  If not, see
   <http://www.gnu.org/licenses/>
 */
-
 /**
- * @file lib/testing_api_cmd_pay.c
- * @brief command to test the /pay feature.
+ * @file lib/testing_api_cmd_pay_order.c
+ * @brief command to test the /orders/ID/pay feature.
  * @author Marcello Stanisci
+ * @author Christian Grothoff
  */
-
 #include "platform.h"
 #include <taler/taler_exchange_service.h>
 #include <taler/taler_testing_lib.h>
@@ -30,9 +29,6 @@
 #include "taler_merchant_service.h"
 #include "taler_merchant_testing_lib.h"
 
-#define AMOUNT_WITH_FEE 0
-#define AMOUNT_WITHOUT_FEE 1
-#define REFUND_FEE 2
 
 /**
  * State for a /pay CMD.
@@ -82,58 +78,10 @@ struct PayState
   const char *amount_without_fee;
 
   /**
-   * Fee for refunding this payment.
-   */
-  const char *refund_fee;
-
-  /**
-   * Handle to the /pay operation.
-   */
-  struct TALER_MERCHANT_Pay *po;
-
-};
-
-
-/**
- * State for a "pay again" CMD.
- */
-struct PayAgainState
-{
-
-  /**
-   * Expected HTTP response code.
-   */
-  unsigned int http_status;
-
-  /**
-   * Reference to the "pay" command to abort.
-   */
-  const char *pay_reference;
-
-  /**
-   * Reference to the coins to use.
-   */
-  const char *coin_reference;
-
-  /**
-   * Merchant URL.
-   */
-  const char *merchant_url;
-
-  /**
-   * Refund fee.
+   * Handle to the pay operation.
    */
-  const char *refund_fee;
+  struct TALER_MERCHANT_OrderPayHandle *oph;
 
-  /**
-   * Handle to a "pay again" operation.
-   */
-  struct TALER_MERCHANT_Pay *pao;
-
-  /**
-   * Interpreter state.
-   */
-  struct TALER_TESTING_Interpreter *is;
 };
 
 
@@ -149,7 +97,6 @@ struct PayAgainState
  * @param amount_with_fee total amount to be paid for a contract.
  * @param amount_without_fee to be removed, there is no
  *        per-contract fee, only per-coin exists.
- * @param refund_fee per-contract? per-coin?
  * @return #GNUNET_OK on success
  */
 static int
@@ -158,8 +105,7 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
              char *coins,
              struct TALER_TESTING_Interpreter *is,
              const char *amount_with_fee,
-             const char *amount_without_fee,
-             const char *refund_fee)
+             const char *amount_without_fee)
 {
   char *token;
 
@@ -244,9 +190,6 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
                    TALER_TESTING_get_trait_url (coin_cmd,
                                                 
TALER_TESTING_UT_EXCHANGE_BASE_URL,
                                                 &icoin->exchange_url));
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_string_to_amount (refund_fee,
-                                           &icoin->refund_fee));
   }
 
   return GNUNET_OK;
@@ -271,7 +214,7 @@ pay_cb (void *cls,
   unsigned int error_line;
   const struct TALER_MerchantPublicKeyP *merchant_pub;
 
-  ps->po = NULL;
+  ps->oph = NULL;
   if (ps->http_status != hr->http_status)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -288,17 +231,28 @@ pay_cb (void *cls,
     struct GNUNET_JSON_Specification spec[] = {
       GNUNET_JSON_spec_fixed_auto ("sig",
                                    &sig),
-      GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
-                                   &ps->h_contract_terms),
       GNUNET_JSON_spec_end ()
     };
     const struct TALER_TESTING_Command *proposal_cmd;
 
-    GNUNET_assert (GNUNET_OK ==
-                   GNUNET_JSON_parse (hr->reply,
-                                      spec,
-                                      &error_name,
-                                      &error_line));
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (hr->reply,
+                           spec,
+                           &error_name,
+                           &error_line))
+    {
+      char *js;
+
+      js = json_dumps (hr->reply,
+                       JSON_INDENT (1));
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Parser failed on %s:%u for input `%s'\n",
+                  error_name,
+                  error_line,
+                  js);
+      free (js);
+      TALER_TESTING_FAIL (ps->is);
+    }
     mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
     mr.purpose.size = htonl (sizeof (mr));
     mr.h_contract_terms = ps->h_contract_terms;
@@ -331,40 +285,18 @@ pay_cb (void *cls,
 
 
 /**
- * Function used by both "pay" and "abort" operations.
- * It prepares data and sends the "pay" request to the
- * backend.
+ * Run a "pay" CMD.
  *
- * @param merchant_url base URL of the merchant serving the
- *        request.
- * @param coin_reference reference to the CMD(s) that offer
- *        "coins" traits.  It is possible to give multiple
- *        references by using semicolons to separate them.
- * @param proposal_refere reference to a "proposal" CMD.
+ * @param cls closure.
+ * @param cmd current CMD being run.
  * @param is interpreter state.
- * @param amount_with_fee amount to be paid, including deposit
- *        fee.
- * @param amount_without_fee amount to be paid, without deposit
- *        fee.
- * @param refund_fee refund fee.
- * @param api_func "lib" function that will be called to either
- *        issue a "pay" or "abort" request.
- * @param api_cb callback for @a api_func.
- * @param api_cb_cls closure for @a api_cb
- *
- * @return handle to the operation, NULL if errors occur.
  */
-static struct TALER_MERCHANT_Pay *
-_pay_run (const char *merchant_url,
-          const char *coin_reference,
-          const char *proposal_reference,
-          struct TALER_TESTING_Interpreter *is,
-          const char *amount_with_fee,
-          const char *amount_without_fee,
-          const char *refund_fee,
-          TALER_MERCHANT_PayCallback api_cb,
-          void *api_cb_cls)
+static void
+pay_run (void *cls,
+         const struct TALER_TESTING_Command *cmd,
+         struct TALER_TESTING_Interpreter *is)
 {
+  struct PayState *ps = cls;
   const struct TALER_TESTING_Command *proposal_cmd;
   const json_t *contract_terms;
   const char *order_id;
@@ -380,27 +312,21 @@ _pay_run (const char *merchant_url,
   unsigned int error_line;
   struct TALER_MERCHANT_PayCoin *pay_coins;
   unsigned int npay_coins;
-  char *cr;
   struct TALER_MerchantSignatureP *merchant_sig;
-  struct TALER_MERCHANT_Pay *ret;
 
-  proposal_cmd = TALER_TESTING_interpreter_lookup_command (is,
-                                                           proposal_reference);
+  ps->is = is;
+  proposal_cmd = TALER_TESTING_interpreter_lookup_command (
+    is,
+    ps->proposal_reference);
 
   if (NULL == proposal_cmd)
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
+    TALER_TESTING_FAIL (is);
 
   if (GNUNET_OK !=
       TALER_TESTING_get_trait_contract_terms (proposal_cmd,
                                               0,
                                               &contract_terms))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
+    TALER_TESTING_FAIL (is);
   {
     /* Get information that needs to be put verbatim in the
      * deposit permission */
@@ -440,96 +366,65 @@ _pay_run (const char *merchant_url,
                   error_line,
                   js);
       free (js);
-      GNUNET_break_op (0);
-      return NULL;
+      TALER_TESTING_FAIL (is);
     }
   }
 
-  cr = GNUNET_strdup (coin_reference);
-  pay_coins = NULL;
-  npay_coins = 0;
-  if (GNUNET_OK !=
-      build_coins (&pay_coins,
-                   &npay_coins,
-                   cr,
-                   is,
-                   amount_with_fee,
-                   amount_without_fee,
-                   refund_fee))
   {
-    GNUNET_array_grow (pay_coins,
-                       npay_coins,
-                       0);
+    char *cr;
+
+    cr = GNUNET_strdup (ps->coin_reference);
+    pay_coins = NULL;
+    npay_coins = 0;
+    if (GNUNET_OK !=
+        build_coins (&pay_coins,
+                     &npay_coins,
+                     cr,
+                     is,
+                     ps->amount_with_fee,
+                     ps->amount_without_fee))
+    {
+      GNUNET_array_grow (pay_coins,
+                         npay_coins,
+                         0);
+      GNUNET_free (cr);
+      TALER_TESTING_FAIL (is);
+    }
     GNUNET_free (cr);
-    GNUNET_break (0);
-    return NULL;
   }
-
-  GNUNET_free (cr);
   if (GNUNET_OK !=
       TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
                                             0,
                                             &merchant_sig))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
+    TALER_TESTING_FAIL (is);
+
   if (GNUNET_OK !=
       TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
                                                 0,
                                                 &h_proposal))
-  {
-    GNUNET_break (0);
-    return NULL;
-  }
-  ret = TALER_MERCHANT_pay_wallet (is->ctx,
-                                   merchant_url,
-                                   h_proposal,
-                                   &total_amount,
-                                   &max_fee,
-                                   &merchant_pub,
-                                   merchant_sig,
-                                   timestamp,
-                                   refund_deadline,
-                                   pay_deadline,
-                                   &h_wire,
-                                   order_id,
-                                   npay_coins,
-                                   pay_coins,
-                                   api_cb,
-                                   api_cb_cls);
+    TALER_TESTING_FAIL (is);
+  ps->h_contract_terms = *h_proposal;
+  ps->oph = TALER_MERCHANT_order_pay (is->ctx,
+                                      ps->merchant_url,
+                                      "", /* session ID */
+                                      h_proposal,
+                                      &total_amount,
+                                      &max_fee,
+                                      &merchant_pub,
+                                      merchant_sig,
+                                      timestamp,
+                                      refund_deadline,
+                                      pay_deadline,
+                                      &h_wire,
+                                      order_id,
+                                      npay_coins,
+                                      pay_coins,
+                                      &pay_cb,
+                                      ps);
   GNUNET_array_grow (pay_coins,
                      npay_coins,
                      0);
-  return ret;
-}
-
-
-/**
- * Run a "pay" CMD.
- *
- * @param cls closure.
- * @param cmd current CMD being run.
- * @param is interpreter state.
- */
-static void
-pay_run (void *cls,
-         const struct TALER_TESTING_Command *cmd,
-         struct TALER_TESTING_Interpreter *is)
-{
-
-  struct PayState *ps = cls;
-
-  ps->is = is;
-  if (NULL == (ps->po = _pay_run (ps->merchant_url,
-                                  ps->coin_reference,
-                                  ps->proposal_reference,
-                                  is,
-                                  ps->amount_with_fee,
-                                  ps->amount_without_fee,
-                                  ps->refund_fee,
-                                  &pay_cb,
-                                  ps)))
+  if (NULL == ps->oph)
     TALER_TESTING_FAIL (is);
 }
 
@@ -546,13 +441,13 @@ pay_cleanup (void *cls,
 {
   struct PayState *ps = cls;
 
-  if (NULL != ps->po)
+  if (NULL != ps->oph)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Command `%s' did not complete.\n",
                 TALER_TESTING_interpreter_get_current_label (
                   ps->is));
-    TALER_MERCHANT_pay_cancel (ps->po);
+    TALER_MERCHANT_order_pay_cancel (ps->oph);
   }
 
   GNUNET_free (ps);
@@ -605,18 +500,16 @@ pay_traits (void *cls,
   }
   {
     struct TALER_TESTING_Trait traits[] = {
-      TALER_TESTING_make_trait_string
-        (AMOUNT_WITH_FEE, ps->amount_with_fee),
-      TALER_TESTING_make_trait_string
-        (AMOUNT_WITHOUT_FEE, ps->amount_without_fee),
-      TALER_TESTING_make_trait_string
-        (REFUND_FEE, ps->refund_fee),
-      TALER_TESTING_make_trait_proposal_reference
-        (0, ps->proposal_reference),
-      TALER_TESTING_make_trait_coin_reference
-        (0, ps->coin_reference),
-      TALER_TESTING_make_trait_order_id (0, order_id),
-      TALER_TESTING_make_trait_merchant_pub (0, merchant_pub),
+      TALER_TESTING_make_trait_proposal_reference (0,
+                                                   ps->proposal_reference),
+      TALER_TESTING_make_trait_coin_reference (0,
+                                               ps->coin_reference),
+      TALER_TESTING_make_trait_order_id (0,
+                                         order_id),
+      TALER_TESTING_make_trait_merchant_pub (0,
+                                             merchant_pub),
+      TALER_TESTING_make_trait_string (0,
+                                       ps->amount_with_fee),
       TALER_TESTING_trait_end ()
     };
 
@@ -641,19 +534,16 @@ pay_traits (void *cls,
  * @param amount_with_fee amount to pay, including the deposit
  *        fee
  * @param amount_without_fee amount to pay, no fees included.
- * @param refund_fee fee for refunding this payment.
- *
  * @return the command
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay (const char *label,
-                       const char *merchant_url,
-                       unsigned int http_status,
-                       const char *proposal_reference,
-                       const char *coin_reference,
-                       const char *amount_with_fee,
-                       const char *amount_without_fee,
-                       const char *refund_fee)
+TALER_TESTING_cmd_merchant_pay_order (const char *label,
+                                      const char *merchant_url,
+                                      unsigned int http_status,
+                                      const char *proposal_reference,
+                                      const char *coin_reference,
+                                      const char *amount_with_fee,
+                                      const char *amount_without_fee)
 {
   struct PayState *ps;
 
@@ -664,7 +554,6 @@ TALER_TESTING_cmd_pay (const char *label,
   ps->merchant_url = merchant_url;
   ps->amount_with_fee = amount_with_fee;
   ps->amount_without_fee = amount_without_fee;
-  ps->refund_fee = refund_fee;
   {
     struct TALER_TESTING_Command cmd = {
       .cls = ps,
@@ -679,197 +568,4 @@ TALER_TESTING_cmd_pay (const char *label,
 }
 
 
-/**
- * Function called with the result of a /pay again operation,
- * check signature and HTTP response code are good.
- *
- * @param cls closure with the interpreter state
- * @param hr HTTP response
- */
-static void
-pay_again_cb (void *cls,
-              const struct TALER_MERCHANT_HttpResponse *hr)
-{
-  struct PayAgainState *pas = cls;
-  struct GNUNET_CRYPTO_EddsaSignature sig;
-  const char *error_name;
-  unsigned int error_line;
-  const struct TALER_TESTING_Command *pay_cmd;
-  const struct TALER_MerchantPublicKeyP *merchant_pub;
-
-  pas->pao = NULL;
-  if (pas->http_status != hr->http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Unexpected response code %u (%d) to command %s\n",
-                hr->http_status,
-                (int) hr->ec,
-                TALER_TESTING_interpreter_get_current_label (pas->is));
-    TALER_TESTING_interpreter_fail (pas->is);
-    return;
-  }
-
-  if (NULL ==
-      (pay_cmd = TALER_TESTING_interpreter_lookup_command (pas->is,
-                                                           
pas->pay_reference)))
-    TALER_TESTING_FAIL (pas->is);
-
-  if (MHD_HTTP_OK == hr->http_status)
-  {
-    struct PaymentResponsePS mr;
-    /* Check signature */
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_fixed_auto ("sig",
-                                   &sig),
-      GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
-                                   &mr.h_contract_terms),
-      GNUNET_JSON_spec_end ()
-    };
-
-    GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (hr->reply,
-                                                   spec,
-                                                   &error_name,
-                                                   &error_line));
-    mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
-    mr.purpose.size = htonl (sizeof (mr));
-
-    if (GNUNET_OK !=
-        TALER_TESTING_get_trait_merchant_pub (pay_cmd,
-                                              0,
-                                              &merchant_pub))
-      TALER_TESTING_FAIL (pas->is);
-
-    if (GNUNET_OK !=
-        GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
-                                    &mr,
-                                    &sig,
-                                    &merchant_pub->eddsa_pub))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Merchant signature given in"
-                  " response to /pay invalid\n");
-      TALER_TESTING_FAIL (pas->is);
-    }
-  }
-
-  TALER_TESTING_interpreter_next (pas->is);
-}
-
-
-/**
- * Run a "pay again" CMD.
- *
- * @param cls closure.
- * @param cmd command currently being run.
- * @param is interpreter state.
- */
-static void
-pay_again_run (void *cls,
-               const struct TALER_TESTING_Command *cmd,
-               struct TALER_TESTING_Interpreter *is)
-{
-  struct PayAgainState *pas = cls;
-  const struct TALER_TESTING_Command *pay_cmd;
-  const char *proposal_reference;
-  const char *amount_with_fee;
-  const char *amount_without_fee;
-
-  pas->is = is;
-  pay_cmd = TALER_TESTING_interpreter_lookup_command
-              (is, pas->pay_reference);
-  if (NULL == pay_cmd)
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_proposal_reference
-        (pay_cmd, 0, &proposal_reference))
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_string
-        (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
-    TALER_TESTING_FAIL (is);
-
-  if (GNUNET_OK != TALER_TESTING_get_trait_string
-        (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
-    TALER_TESTING_FAIL (is);
-
-  if (NULL == (pas->pao = _pay_run (pas->merchant_url,
-                                    pas->coin_reference,
-                                    proposal_reference,
-                                    is,
-                                    amount_with_fee,
-                                    amount_without_fee,
-                                    pas->refund_fee,
-                                    &pay_again_cb,
-                                    pas)))
-    TALER_TESTING_FAIL (is);
-}
-
-
-/**
- * Free and possibly cancel a "pay again" CMD.
- *
- * @param cls closure.
- * @param cmd command currently being freed.
- */
-static void
-pay_again_cleanup (void *cls,
-                   const struct TALER_TESTING_Command *cmd)
-{
-  struct PayAgainState *pas = cls;
-
-  if (NULL != pas->pao)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command `%s' did not complete.\n",
-                TALER_TESTING_interpreter_get_current_label (
-                  pas->is));
-    TALER_MERCHANT_pay_cancel (pas->pao);
-  }
-  GNUNET_free (pas);
-}
-
-
-/**
- * Make a "pay again" test command.  Its purpose is to
- * take all the data from a aborted "pay" CMD, and use
- * good coins - found in @a coin_reference - to correctly
- * pay for it.
- *
- * @param label command label
- * @param merchant_url merchant base URL
- * @param pay_reference reference to the payment to replay
- * @param coin_reference reference to the coins to use
- * @param http_status expected HTTP response code
- *
- * @return the command
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_pay_again (const char *label,
-                             const char *merchant_url,
-                             const char *pay_reference,
-                             const char *coin_reference,
-                             const char *refund_fee,
-                             unsigned int http_status)
-{
-  struct PayAgainState *pas;
-
-  pas = GNUNET_new (struct PayAgainState);
-  pas->http_status = http_status;
-  pas->pay_reference = pay_reference;
-  pas->coin_reference = coin_reference;
-  pas->merchant_url = merchant_url;
-  pas->refund_fee = refund_fee;
-  {
-    struct TALER_TESTING_Command cmd = {
-      .cls = pas,
-      .label = label,
-      .run = &pay_again_run,
-      .cleanup = &pay_again_cleanup
-    };
-
-    return cmd;
-  }
-}
-
-
-/* end of testing_api_cmd_pay.c */
+/* end of testing_api_cmd_pay_order.c */
diff --git a/src/testing/testing_api_cmd_post_instances.c 
b/src/testing/testing_api_cmd_post_instances.c
new file mode 100644
index 0000000..b2e21bf
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_instances.c
@@ -0,0 +1,394 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_post_instances.c
+ * @brief command to test POST /instances
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /instances" CMD.
+ */
+struct PostInstancesState
+{
+
+  /**
+   * Handle for a "POST instance" request.
+   */
+  struct TALER_MERCHANT_InstancesPostHandle *iph;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the instance to run POST for.
+   */
+  const char *instance_id;
+
+  /**
+   * Length of the @payto_uris array
+   */
+  unsigned int payto_uris_length;
+
+  /**
+   * Array of payto URIs.
+   */
+  const char **payto_uris;
+
+  /**
+   * Name of the instance.
+   */
+  const char *name;
+
+  /**
+   * Address to use.
+   */
+  json_t *address;
+
+  /**
+   * Jurisdiction to use.
+   */
+  json_t *jurisdiction;
+
+  /**
+   * Wire fee to use.
+   */
+  struct TALER_Amount default_max_wire_fee;
+
+  /**
+   * Amortization to use.
+   */
+  uint32_t default_wire_fee_amortization;
+
+  /**
+   * Deposit fee ceiling to use.
+   */
+  struct TALER_Amount default_max_deposit_fee;
+
+  /**
+   * Wire transfer delay to use.
+   */
+  struct GNUNET_TIME_Relative default_wire_transfer_delay;
+
+  /**
+   * Order validity default duration to use.
+   */
+  struct GNUNET_TIME_Relative default_pay_delay;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /instances operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+post_instances_cb (void *cls,
+                   const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct PostInstancesState *pis = cls;
+
+  pis->iph = NULL;
+  if (pis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pis->is));
+    TALER_TESTING_interpreter_fail (pis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_CONFLICT:
+    break;
+  // FIXME: add other legitimate states here...
+  default:
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "POST /instances" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_instances_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct PostInstancesState *pis = cls;
+
+  pis->is = is;
+  pis->iph = TALER_MERCHANT_instances_post (is->ctx,
+                                            pis->merchant_url,
+                                            pis->instance_id,
+                                            pis->payto_uris_length,
+                                            pis->payto_uris,
+                                            pis->name,
+                                            pis->address,
+                                            pis->jurisdiction,
+                                            &pis->default_max_wire_fee,
+                                            pis->default_wire_fee_amortization,
+                                            &pis->default_max_deposit_fee,
+                                            pis->default_wire_transfer_delay,
+                                            pis->default_pay_delay,
+                                            &post_instances_cb,
+                                            pis);
+  GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Offers information from the POST /instances CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+post_instances_traits (void *cls,
+                       const void **ret,
+                       const char *trait,
+                       unsigned int index)
+{
+  struct PostInstancesState *pis = cls;
+  #define NUM_TRAITS (pis->payto_uris_length) + 11
+  struct TALER_TESTING_Trait traits[NUM_TRAITS];
+  traits[0] =
+    TALER_TESTING_make_trait_string (0, pis->name);
+  traits[1] =
+    TALER_TESTING_make_trait_string (1, pis->instance_id);
+  traits[2] =
+    TALER_TESTING_make_trait_json (0, pis->address);
+  traits[3] =
+    TALER_TESTING_make_trait_json (1, pis->jurisdiction);
+  traits[4] =
+    TALER_TESTING_make_trait_amount_obj (0, &pis->default_max_wire_fee);
+  traits[5] =
+    TALER_TESTING_make_trait_uint32 (0, &pis->default_wire_fee_amortization);
+  traits[6] =
+    TALER_TESTING_make_trait_amount_obj (1, &pis->default_max_deposit_fee);
+  traits[7] =
+    TALER_TESTING_make_trait_relative_time (0,
+                                            &pis->default_wire_transfer_delay);
+  traits[8] =
+    TALER_TESTING_make_trait_relative_time (1, &pis->default_pay_delay);
+  traits[9] =
+    TALER_TESTING_make_trait_uint32 (1, &pis->payto_uris_length);
+  traits[NUM_TRAITS - 1] =
+    TALER_TESTING_trait_end ();
+  for (unsigned int i = 0; i < pis->payto_uris_length; ++i)
+  {
+    traits[10 + i] =
+      TALER_TESTING_make_trait_string (2 + i, pis->payto_uris[i]);
+  }
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Free the state of a "POST /instances" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_instances_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct PostInstancesState *pis = cls;
+
+  if (NULL != pis->iph)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "POST /instances operation did not complete\n");
+    TALER_MERCHANT_instances_post_cancel (pis->iph);
+  }
+  json_decref (pis->address);
+  json_decref (pis->jurisdiction);
+  GNUNET_free (pis->payto_uris);
+  GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "POST /instances" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /instances request.
+ * @param instance_id the ID of the instance to query
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant instance
+ * @param name name of the merchant instance
+ * @param address physical address of the merchant instance
+ * @param jurisdiction jurisdiction of the merchant instance
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to 
fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess 
wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is 
willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant 
will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_instances2 (
+  const char *label,
+  const char *merchant_url,
+  const char *instance_id,
+  unsigned int payto_uris_length,
+  const char *payto_uris[],
+  const char *name,
+  json_t *address,
+  json_t *jurisdiction,
+  const char *default_max_wire_fee,
+  uint32_t default_wire_fee_amortization,
+  const char *default_max_deposit_fee,
+  struct GNUNET_TIME_Relative default_wire_transfer_delay,
+  struct GNUNET_TIME_Relative default_pay_delay,
+  unsigned int http_status)
+{
+  struct PostInstancesState *pis;
+
+  pis = GNUNET_new (struct PostInstancesState);
+  pis->merchant_url = merchant_url;
+  pis->instance_id = instance_id;
+  pis->http_status = http_status;
+  pis->payto_uris_length = payto_uris_length;
+  pis->payto_uris = GNUNET_new_array (payto_uris_length,
+                                      const char *);
+  memcpy (pis->payto_uris,
+          payto_uris,
+          sizeof (const char *) * payto_uris_length);
+  pis->name = name;
+  pis->address = address; /* ownership transfer! */
+  pis->jurisdiction = jurisdiction; /* ownership transfer! */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (default_max_wire_fee,
+                                         &pis->default_max_wire_fee));
+  pis->default_wire_fee_amortization = default_wire_fee_amortization;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (default_max_deposit_fee,
+                                         &pis->default_max_deposit_fee));
+  pis->default_wire_transfer_delay = default_wire_transfer_delay;
+  pis->default_pay_delay = default_pay_delay;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pis,
+      .label = label,
+      .run = &post_instances_run,
+      .cleanup = &post_instances_cleanup,
+      .traits = &post_instances_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Define a "POST /instances" CMD, simple version
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /instances request.
+ * @param instance_id the ID of the instance to create
+ * @param payto_uri payment URI to use
+ * @param currency currency to use for default fees
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_instances (const char *label,
+                                           const char *merchant_url,
+                                           const char *instance_id,
+                                           const char *payto_uri,
+                                           const char *currency,
+                                           unsigned int http_status)
+{
+  const char *payto_uris[] = {
+    payto_uri
+  };
+  struct TALER_Amount default_max_fee;
+  const char *default_max_fee_s;
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_get_zero (currency,
+                                        &default_max_fee));
+  default_max_fee.value = 1;
+  default_max_fee_s = TALER_amount2s (&default_max_fee);
+
+  return TALER_TESTING_cmd_merchant_post_instances2 (
+    label,
+    merchant_url,
+    instance_id,
+    1,
+    payto_uris,
+    instance_id,
+    json_pack ("{s:s}", "city", "shopcity"),
+    json_pack ("{s:s}", "city", "lawyercity"),
+    default_max_fee_s,
+    10,
+    default_max_fee_s,
+    GNUNET_TIME_UNIT_ZERO, /* no wire transfer delay */
+    GNUNET_TIME_UNIT_MINUTES,
+    http_status);
+}
+
+
+/* end of testing_api_cmd_post_instance.c */
diff --git a/src/lib/testing_api_cmd_proposal.c 
b/src/testing/testing_api_cmd_post_orders.c
similarity index 64%
rename from src/lib/testing_api_cmd_proposal.c
rename to src/testing/testing_api_cmd_post_orders.c
index fd53db2..0a1db61 100644
--- a/src/lib/testing_api_cmd_proposal.c
+++ b/src/testing/testing_api_cmd_post_orders.c
@@ -18,8 +18,8 @@
 */
 
 /**
- * @file exchange/testing_api_cmd_proposal.c
- * @brief command to run /proposal
+ * @file testing_api_cmd_post_orders.c
+ * @brief command to run POST /orders
  * @author Marcello Stanisci
  */
 
@@ -30,9 +30,9 @@
 #include "taler_merchant_testing_lib.h"
 
 /**
- * State for a "proposal" CMD.
+ * State for a "POST /orders" CMD.
  */
-struct ProposalState
+struct OrdersState
 {
 
   /**
@@ -61,17 +61,16 @@ struct ProposalState
   struct GNUNET_HashCode h_contract_terms;
 
   /**
-   * The /proposal operation handle.
+   * The /orders operation handle.
    */
-  struct TALER_MERCHANT_ProposalOperation *po;
+  struct TALER_MERCHANT_PostOrdersOperation *po;
 
   /**
-   * The (initial) /proposal/lookup operation handle.
-   * The logic is such that after a proposal creation,
-   * it soon makes a proposal lookup in order to check
-   * if the merchant backend is actually aware.
+   * The (initial) POST /orders/$ID/claim operation handle.
+   * The logic is such that after an order creation,
+   * we immediately claim the order.
    */
-  struct TALER_MERCHANT_ProposalLookupOperation *plo;
+  struct TALER_MERCHANT_OrderClaimHandle *och;
 
   /**
    * The nonce.
@@ -89,7 +88,7 @@ struct ProposalState
   struct TALER_TESTING_Interpreter *is;
 
   /**
-   * Merchant signature over the proposal.
+   * Merchant signature over the orders.
    */
   struct TALER_MerchantSignatureP merchant_sig;
 
@@ -110,22 +109,20 @@ struct ProposalState
  * @return #GNUNET_OK on success
  */
 static int
-proposal_traits (void *cls,
-                 const void **ret,
-                 const char *trait,
-                 unsigned int index)
+orders_traits (void *cls,
+               const void **ret,
+               const char *trait,
+               unsigned int index)
 {
-  struct ProposalState *ps = cls;
-#define MAKE_TRAIT_NONCE(ptr)                       \
-  TALER_TESTING_make_trait_merchant_pub (1, (struct \
-                                             TALER_MerchantPublicKeyP *) (ptr))
+  struct OrdersState *ps = cls;
+
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_order_id (0, ps->order_id),
     TALER_TESTING_make_trait_contract_terms (0, ps->contract_terms),
     TALER_TESTING_make_trait_h_contract_terms (0, &ps->h_contract_terms),
     TALER_TESTING_make_trait_merchant_sig (0, &ps->merchant_sig),
     TALER_TESTING_make_trait_merchant_pub (0, &ps->merchant_pub),
-    MAKE_TRAIT_NONCE (&ps->nonce),
+    TALER_TESTING_make_trait_claim_nonce (0, &ps->nonce),
     TALER_TESTING_trait_end ()
   };
 
@@ -137,8 +134,8 @@ proposal_traits (void *cls,
 
 
 /**
- * Used to fill the "proposal" CMD state with backend-provided
- * values.  Also double-checks that the proposal was correctly
+ * Used to fill the "orders" CMD state with backend-provided
+ * values.  Also double-checks that the order was correctly
  * created.
  *
  * @param cls closure
@@ -147,13 +144,13 @@ proposal_traits (void *cls,
  * @param hash hash over the contract
  */
 static void
-proposal_lookup_initial_cb (void *cls,
-                            const struct TALER_MERCHANT_HttpResponse *hr,
-                            const json_t *contract_terms,
-                            const struct TALER_MerchantSignatureP *sig,
-                            const struct GNUNET_HashCode *hash)
+orders_claim_cb (void *cls,
+                 const struct TALER_MERCHANT_HttpResponse *hr,
+                 const json_t *contract_terms,
+                 const struct TALER_MerchantSignatureP *sig,
+                 const struct GNUNET_HashCode *hash)
 {
-  struct ProposalState *ps = cls;
+  struct OrdersState *ps = cls;
   struct TALER_MerchantPublicKeyP merchant_pub;
   const char *error_name;
   unsigned int error_line;
@@ -163,7 +160,7 @@ proposal_lookup_initial_cb (void *cls,
     GNUNET_JSON_spec_end ()
   };
 
-  ps->plo = NULL;
+  ps->och = NULL;
   if (ps->http_status != hr->http_status)
     TALER_TESTING_FAIL (ps->is);
 
@@ -197,20 +194,20 @@ proposal_lookup_initial_cb (void *cls,
 
 /**
  * Callback that processes the response following a
- * proposal's put.  NOTE: no contract terms are included
- * here; they need to be taken via the "proposal lookup"
+ * POST /orders.  NOTE: no contract terms are included
+ * here; they need to be taken via the "orders lookup"
  * method.
  *
  * @param cls closure.
  * @param hr HTTP response
- * @param order_id order id of the proposal.
+ * @param order_id order id of the orders.
  */
 static void
-proposal_cb (void *cls,
-             const struct TALER_MERCHANT_HttpResponse *hr,
-             const char *order_id)
+order_cb (void *cls,
+          const struct TALER_MERCHANT_HttpResponse *hr,
+          const char *order_id)
 {
-  struct ProposalState *ps = cls;
+  struct OrdersState *ps = cls;
 
   ps->po = NULL;
   if (ps->http_status != hr->http_status)
@@ -221,14 +218,12 @@ proposal_cb (void *cls,
                      ps->http_status);
     TALER_TESTING_FAIL (ps->is);
   }
-
   if (0 == ps->http_status)
   {
-    TALER_LOG_DEBUG ("/proposal, expected 0 status code\n");
+    TALER_LOG_DEBUG ("/orders, expected 0 status code\n");
     TALER_TESTING_interpreter_next (ps->is);
     return;
   }
-
   switch (hr->http_status)
   {
   case MHD_HTTP_OK:
@@ -239,12 +234,12 @@ proposal_cb (void *cls,
       char *s = json_dumps (hr->reply,
                             JSON_COMPACT);
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Unexpected status code from /proposal: %u (%d) at %s; JSON: 
%s\n",
+                  "Unexpected status code from /orders: %u (%d) at %s; JSON: 
%s\n",
                   hr->http_status,
                   hr->ec,
                   TALER_TESTING_interpreter_get_current_label (ps->is),
                   s);
-      GNUNET_free_non_null (s);
+      GNUNET_free (s);
       /**
        * Not failing, as test cases are _supposed_
        * to create non 200 OK situations.
@@ -255,29 +250,29 @@ proposal_cb (void *cls,
   }
 
   if (NULL ==
-      (ps->plo = TALER_MERCHANT_proposal_lookup (ps->is->ctx,
-                                                 ps->merchant_url,
-                                                 ps->order_id,
-                                                 &ps->nonce,
-                                                 &proposal_lookup_initial_cb,
-                                                 ps)))
+      (ps->och = TALER_MERCHANT_order_claim (ps->is->ctx,
+                                             ps->merchant_url,
+                                             ps->order_id,
+                                             &ps->nonce,
+                                             &orders_claim_cb,
+                                             ps)))
     TALER_TESTING_FAIL (ps->is);
 }
 
 
 /**
- * Run a "proposal" CMD.
+ * Run a "orders" CMD.
  *
  * @param cls closure.
  * @param cmd command currently being run.
  * @param is interpreter state.
  */
 static void
-proposal_run (void *cls,
-              const struct TALER_TESTING_Command *cmd,
-              struct TALER_TESTING_Interpreter *is)
+orders_run (void *cls,
+            const struct TALER_TESTING_Command *cmd,
+            struct TALER_TESTING_Interpreter *is)
 {
-  struct ProposalState *ps = cls;
+  struct OrdersState *ps = cls;
   json_t *order;
   json_error_t error;
 
@@ -310,80 +305,78 @@ proposal_run (void *cls,
                          json_string (order_id));
     GNUNET_free (order_id);
   }
-
-  GNUNET_CRYPTO_random_block
-    (GNUNET_CRYPTO_QUALITY_WEAK,
-    &ps->nonce,
-    sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
-
-  ps->po = TALER_MERCHANT_order_put (is->ctx,
-                                     ps->merchant_url,
-                                     order,
-                                     &proposal_cb,
-                                     ps);
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &ps->nonce,
+                              sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+  ps->po = TALER_MERCHANT_orders_post (is->ctx,
+                                       ps->merchant_url,
+                                       order,
+                                       GNUNET_TIME_UNIT_ZERO,
+                                       &order_cb,
+                                       ps);
   json_decref (order);
   GNUNET_assert (NULL != ps->po);
 }
 
 
 /**
- * Free the state of a "proposal" CMD, and possibly
+ * Free the state of a "orders" CMD, and possibly
  * cancel it if it did not complete.
  *
  * @param cls closure.
  * @param cmd command being freed.
  */
 static void
-proposal_cleanup (void *cls,
-                  const struct TALER_TESTING_Command *cmd)
+orders_cleanup (void *cls,
+                const struct TALER_TESTING_Command *cmd)
 {
-  struct ProposalState *ps = cls;
+  struct OrdersState *ps = cls;
 
   if (NULL != ps->po)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Command '%s' did not complete (proposal put)\n",
+                "Command '%s' did not complete (orders put)\n",
                 cmd->label);
-    TALER_MERCHANT_proposal_cancel (ps->po);
+    TALER_MERCHANT_orders_post_cancel (ps->po);
     ps->po = NULL;
   }
 
-  if (NULL != ps->plo)
+  if (NULL != ps->och)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Command '%s' did not complete"
-                " (proposal lookup)\n",
+                " (orders lookup)\n",
                 cmd->label);
-    TALER_MERCHANT_proposal_lookup_cancel (ps->plo);
-    ps->plo = NULL;
+    TALER_MERCHANT_order_claim_cancel (ps->och);
+    ps->och = NULL;
   }
 
   json_decref (ps->contract_terms);
-  GNUNET_free_non_null ((void *) ps->order_id);
+  GNUNET_free_nz ((void *) ps->order_id);
   GNUNET_free (ps);
 }
 
 
 /**
- * Make the "proposal" command.
+ * Make the "orders" command.
  *
  * @param label command label
  * @param merchant_url base URL of the merchant serving
- *        the proposal request.
+ *        the orders request.
  * @param http_status expected HTTP status.
  * @param order the order to PUT to the merchant.
  *
  * @return the command
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_proposal (const char *label,
-                            const char *merchant_url,
-                            unsigned int http_status,
-                            const char *order)
+TALER_TESTING_cmd_merchant_post_orders (const char *label,
+                                        const char *merchant_url,
+                                        unsigned int http_status,
+                                        const char *order)
 {
-  struct ProposalState *ps;
+  struct OrdersState *ps;
 
-  ps = GNUNET_new (struct ProposalState);
+  ps = GNUNET_new (struct OrdersState);
   ps->order = order;
   ps->http_status = http_status;
   ps->merchant_url = merchant_url;
@@ -391,9 +384,9 @@ TALER_TESTING_cmd_proposal (const char *label,
     struct TALER_TESTING_Command cmd = {
       .cls = ps,
       .label = label,
-      .run = &proposal_run,
-      .cleanup = &proposal_cleanup,
-      .traits = &proposal_traits
+      .run = &orders_run,
+      .cleanup = &orders_cleanup,
+      .traits = &orders_traits
     };
 
     return cmd;
diff --git a/src/testing/testing_api_cmd_post_products.c 
b/src/testing/testing_api_cmd_post_products.c
new file mode 100644
index 0000000..671444a
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_products.c
@@ -0,0 +1,354 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_post_products.c
+ * @brief command to test POST /products
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /products" CMD.
+ */
+struct PostProductsState
+{
+
+  /**
+   * Handle for a "GET product" request.
+   */
+  struct TALER_MERCHANT_ProductsPostHandle *iph;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * ID of the product to run POST for.
+   */
+  const char *product_id;
+
+  /**
+   * description of the product
+   */
+  const char *description;
+
+  /**
+   * Map from IETF BCP 47 language tags to localized descriptions
+   */
+  json_t *description_i18n;
+
+  /**
+   * unit in which the product is measured (liters, kilograms, packages, etc.)
+   */
+  const char *unit;
+
+  /**
+   * the price for one @a unit of the product
+   */
+  struct TALER_Amount price;
+
+  /**
+   * base64-encoded product image
+   */
+  json_t *image;
+
+  /**
+   * list of taxes paid by the merchant
+   */
+  json_t *taxes;
+
+  /**
+   * in @e units, -1 to indicate "infinite" (i.e. electronic books)
+   */
+  int64_t total_stock;
+
+  /**
+   * where the product is in stock
+   */
+  json_t *address;
+
+  /**
+   * when the next restocking is expected to happen, 0 for unknown,
+   */
+  struct GNUNET_TIME_Absolute next_restock;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /products operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+post_products_cb (void *cls,
+                  const struct TALER_MERCHANT_HttpResponse *hr)
+{
+  struct PostProductsState *pis = cls;
+
+  pis->iph = NULL;
+  if (pis->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pis->is));
+    TALER_TESTING_interpreter_fail (pis->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_CONFLICT:
+    break;
+  // FIXME: add other legitimate states here...
+  default:
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "POST /products" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_products_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct PostProductsState *pis = cls;
+
+  pis->is = is;
+  pis->iph = TALER_MERCHANT_products_post (is->ctx,
+                                           pis->merchant_url,
+                                           pis->product_id,
+                                           pis->description,
+                                           pis->description_i18n,
+                                           pis->unit,
+                                           &pis->price,
+                                           pis->image,
+                                           pis->taxes,
+                                           pis->total_stock,
+                                           pis->address,
+                                           pis->next_restock,
+                                           &post_products_cb,
+                                           pis);
+  GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Offers information from the POST /products CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+post_products_traits (void *cls,
+                      const void **ret,
+                      const char *trait,
+                      unsigned int index)
+{
+  struct PostProductsState *pps = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_string (0, pps->description),
+    TALER_TESTING_make_trait_json (0, pps->description_i18n),
+    TALER_TESTING_make_trait_string (1, pps->unit),
+    TALER_TESTING_make_trait_amount_obj (0, &pps->price),
+    TALER_TESTING_make_trait_json (1, pps->image),
+    TALER_TESTING_make_trait_json (2, pps->taxes),
+    TALER_TESTING_make_trait_int64 (0, &pps->total_stock),
+    TALER_TESTING_make_trait_json (3, pps->address),
+    TALER_TESTING_make_trait_absolute_time (0, &pps->next_restock),
+    TALER_TESTING_make_trait_string (2, pps->product_id),
+    TALER_TESTING_trait_end (),
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Free the state of a "POST product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_products_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct PostProductsState *pis = cls;
+
+  if (NULL != pis->iph)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "POST /products operation did not complete\n");
+    TALER_MERCHANT_products_post_cancel (pis->iph);
+  }
+  json_decref (pis->description_i18n);
+  json_decref (pis->image);
+  json_decref (pis->taxes);
+  json_decref (pis->address);
+  GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "POST /products" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /products request.
+ * @param product_id the ID of the product to query
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized 
descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, 
packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to 
imply that
+ *              this product is not sold separately or that the price is not 
fixed and
+ *              must be supplied by the front-end.  If non-zero, price must 
include
+ *              applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic 
books)
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for 
unknown,
+ *                     #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_products2 (
+  const char *label,
+  const char *merchant_url,
+  const char *product_id,
+  const char *description,
+  json_t *description_i18n,
+  const char *unit,
+  const char *price,
+  json_t *image,
+  json_t *taxes,
+  int64_t total_stock,
+  json_t *address,
+  struct GNUNET_TIME_Absolute next_restock,
+  unsigned int http_status)
+{
+  struct PostProductsState *pis;
+
+  pis = GNUNET_new (struct PostProductsState);
+  pis->merchant_url = merchant_url;
+  pis->product_id = product_id;
+  pis->http_status = http_status;
+  pis->description = description;
+  pis->description_i18n = description_i18n; /* ownership taken */
+  pis->unit = unit;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (price,
+                                         &pis->price));
+  pis->image = image; /* ownership taken */
+  pis->taxes = taxes; /* ownership taken */
+  pis->total_stock = total_stock;
+  pis->address = address; /* ownership taken */
+  pis->next_restock = next_restock;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pis,
+      .label = label,
+      .run = &post_products_run,
+      .cleanup = &post_products_cleanup,
+      .traits = &post_products_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Define a "POST /products" CMD, simple version
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ *        POST /products request.
+ * @param product_id the ID of the product to create
+ * @param description name of the product
+ * @param price price of the product
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_products (const char *label,
+                                          const char *merchant_url,
+                                          const char *product_id,
+                                          const char *description,
+                                          const char *price,
+                                          unsigned int http_status)
+{
+  return TALER_TESTING_cmd_merchant_post_products2 (
+    label,
+    merchant_url,
+    product_id,
+    description,
+    json_pack ("{s:s}", "en", description),
+    "test-unit",
+    price,
+    json_object (),
+    json_object (),
+    4,
+    json_pack ("{s:s}", "street", "my street"),
+    GNUNET_TIME_UNIT_ZERO_ABS,
+    http_status);
+}
+
+
+/* end of testing_api_cmd_post_products.c */
diff --git a/src/testing/testing_api_cmd_post_reserves.c 
b/src/testing/testing_api_cmd_post_reserves.c
new file mode 100644
index 0000000..eaa7e64
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_reserves.c
@@ -0,0 +1,297 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_post_reserves.c
+ * @brief command to test POST /reserves
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * State of a "POST /reserves" CMD.
+ */
+struct PostReservesState
+{
+  /**
+   * Handle for a "POST /reserves" request.
+   */
+  struct TALER_MERCHANT_PostReservesHandle *prh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant
+   */
+  const char *merchant_url;
+
+  /**
+   * Base URL of the exchange.
+   */
+  const char *exchange_url;
+
+  /**
+   * Wire method for the reserve.
+   */
+  const char *wire_method;
+
+  /**
+   * The initial balance of the reserve.
+   */
+  struct TALER_Amount initial_balance;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Public key assigned to the reserve
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+};
+
+
+/**
+ * Callbacks of this type are used to work the result of submitting a
+ * POST /reserves request to a merchant
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param reserve_pub public key of the created reserve, NULL on error
+ * @param payto_uri where to make the payment to for filling the reserve, NULL 
on error
+ */
+static void
+post_reserves_cb (void *cls,
+                  const struct TALER_MERCHANT_HttpResponse *hr,
+                  const struct TALER_ReservePublicKeyP *reserve_pub,
+                  const char *payto_uri)
+{
+  struct PostReservesState *prs = cls;
+
+  prs->prh = NULL;
+  if (prs->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (prs->is));
+    TALER_TESTING_interpreter_fail (prs->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_NO_CONTENT:
+    break;
+  case MHD_HTTP_CONFLICT:
+    break;
+  case MHD_HTTP_OK:
+    break;
+  // FIXME: add other legitimate states here...
+  default:
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status (%d).\n",
+                hr->http_status);
+  }
+  prs->reserve_pub = *reserve_pub;
+  TALER_TESTING_interpreter_next (prs->is);
+}
+
+
+/**
+ * Offers information from the POST /reserves CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+post_reserves_traits (void *cls,
+                      const void **ret,
+                      const char *trait,
+                      unsigned int index)
+{
+  struct PostReservesState *prs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_reserve_pub (0, &prs->reserve_pub),
+    TALER_TESTING_make_trait_amount_obj (0, &prs->initial_balance),
+    TALER_TESTING_trait_end (),
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Run the "POST /reserves" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_reserves_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct PostReservesState *prs = cls;
+
+  prs->is = is;
+  prs->prh = TALER_MERCHANT_reserves_post (is->ctx,
+                                           prs->merchant_url,
+                                           &prs->initial_balance,
+                                           prs->exchange_url,
+                                           prs->wire_method,
+                                           &post_reserves_cb,
+                                           prs);
+  GNUNET_assert (NULL != prs->prh);
+}
+
+
+/**
+ * Run the fake "POST /reserves" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_reserves_fake_run (void *cls,
+                        const struct TALER_TESTING_Command *cmd,
+                        struct TALER_TESTING_Interpreter *is)
+{
+  struct PostReservesState *prs = cls;
+  struct TALER_ReservePrivateKeyP reserve_priv;
+
+  prs->is = is;
+  GNUNET_CRYPTO_eddsa_key_create (&reserve_priv.eddsa_priv);
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv.eddsa_priv,
+                                      &prs->reserve_pub.eddsa_pub);
+
+  GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:100.00",
+                                                      &prs->initial_balance));
+  TALER_TESTING_interpreter_next (prs->is);
+}
+
+
+/**
+ * Free the state of a "POST /reserves" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_reserves_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct PostReservesState *prs = cls;
+
+  if (NULL != prs->prh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "POST /reserves operation did not complete\n");
+    TALER_MERCHANT_reserves_post_cancel (prs->prh);
+  }
+  GNUNET_free (prs);
+}
+
+
+/**
+ * Define a "POST /reserves" CMD
+ *
+ * @param label command label.
+ * @param merchant_url url to the merchant.
+ * @param initial_balance initial amount in the reserve.
+ * @param exchange_url url to the exchange
+ * @param wire_method wire transfer method to use for this reserve
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_reserves (const char *label,
+                                          const char *merchant_url,
+                                          const char *initial_balance,
+                                          const char *exchange_url,
+                                          const char *wire_method,
+                                          unsigned int http_status)
+{
+  struct PostReservesState *prs;
+
+  prs = GNUNET_new (struct PostReservesState);
+  prs->merchant_url = merchant_url;
+  prs->exchange_url = exchange_url;
+  prs->wire_method = wire_method;
+  prs->http_status = http_status;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (initial_balance,
+                                         &prs->initial_balance));
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = prs,
+      .label = label,
+      .run = &post_reserves_run,
+      .cleanup = &post_reserves_cleanup,
+      .traits = &post_reserves_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * This commands does not query the backend at all,
+ * but just makes up a fake reserve.
+ *
+ * @param label command label.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_reserves_fake (const char *label)
+{
+  struct PostReservesState *prs;
+
+  prs = GNUNET_new (struct PostReservesState);
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = prs,
+      .label = label,
+      .run = &post_reserves_fake_run,
+      .cleanup = &post_reserves_cleanup,
+      .traits = &post_reserves_traits
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_post_transfers.c 
b/src/testing/testing_api_cmd_post_transfers.c
new file mode 100644
index 0000000..53d4911
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_transfers.c
@@ -0,0 +1,518 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_post_transfers.c
+ * @brief command to test POST /transfers
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State of a "POST /transfers" CMD.
+ */
+struct PostTransfersState
+{
+
+  /**
+   * Handle for a "POST /transfers" request.
+   */
+  struct TALER_MERCHANT_PostTransfersHandle *pth;
+
+  /**
+   * Handle for a "GET" bank account history request.
+   */
+  struct TALER_BANK_DebitHistoryHandle *dhh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Base URL of the merchant serving the request.
+   */
+  const char *merchant_url;
+
+  /**
+   * URL of the bank to run history on (set once @e found is set).
+   */
+  char *exchange_url;
+
+  /**
+   * Payto URI to filter on.
+   */
+  const char *payto_uri;
+
+  /**
+   * Authentication details to authenticate to the bank.
+   */
+  struct TALER_BANK_AuthenticationData auth;
+
+  /**
+   * Set once we discovered the WTID and thus @e found is true.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+  /**
+   * the credit amount to look for at @e bank_url.
+   */
+  struct TALER_Amount credit_amount;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Array of deposit command labels we expect to see aggregated.
+   */
+  const char **deposits;
+
+  /**
+   * Length of @e deposits.
+   */
+  unsigned int deposits_length;
+
+  /**
+   * Set to true once @e wtid and @e exchange_url are initialized.
+   */
+  bool found;
+
+  /**
+   * When the exchange executed the transfer.
+   */
+  struct GNUNET_TIME_Absolute execution_time;
+};
+
+
+/**
+ * Callback for a POST /transfers operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param execution_time when did the transfer happen (according to the 
exchange),
+ *          #GNUNET_TIME_UNIT_FOREVER_ABS if the transfer did not yet happen 
or if
+ *          we have no data from the exchange about it
+ * @param total_amount total amount of the wire transfer, or NULL if the 
exchange did
+ *             not provide any details
+ * @param wire_fee how much did the exchange charge in terms of wire fees, or 
NULL
+ *             if the exchange did not provide any details
+ * @param details_length length of the @a details array
+ * @param details array with details about the combined transactions
+ */
+static void
+transfers_cb (void *cls,
+              const struct TALER_MERCHANT_HttpResponse *hr,
+              struct GNUNET_TIME_Absolute execution_time,
+              const struct TALER_Amount *total_amount,
+              const struct TALER_Amount *wire_fee,
+              unsigned int details_length,
+              const struct TALER_MERCHANT_TrackTransferDetail details[])
+{
+  struct PostTransfersState *pts = cls;
+
+  pts->pth = NULL;
+  if (pts->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (pts->is));
+    TALER_TESTING_interpreter_fail (pts->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      // struct TALER_Amount total;
+
+      pts->execution_time = execution_time;
+      /*
+      if (0 >
+          TALER_amount_subtract (&total,
+                                 total_amount,
+                                 wire_fee))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (pts->is);
+        return;
+      }
+      if (0 !=
+          TALER_amount_cmp (&total,
+                            &pts->credit_amount))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (pts->is);
+        return;
+      }
+      TALER_amount_get_zero (total.currency,
+                             &total);
+      for (unsigned int i = 0; i<details_length; i++)
+      {
+        const struct TALER_MERCHANT_TrackTransferDetail *tdd = &details[i];
+        struct TALER_Amount sum;
+        struct TALER_Amount fees;
+
+        TALER_amount_get_zero (tdd->deposit_value.currency,
+                               &sum);
+        TALER_amount_get_zero (tdd->deposit_fee.currency,
+                               &fees);
+        for (unsigned int j = 0; j<pts->deposits_length; j++)
+        {
+          const char *label = pts->deposits[j];
+          const struct TALER_TESTING_Command *cmd;
+          const json_t *contract_terms;
+          const struct TALER_Amount *deposit_value;
+          const struct TALER_Amount *deposit_fee;
+          const char *order_id;
+
+          cmd = TALER_TESTING_interpreter_lookup_command (pts->is,
+                                                          label);
+          if (NULL == cmd)
+          {
+            GNUNET_break (0);
+            TALER_TESTING_interpreter_fail (pts->is);
+            return;
+          }
+          if ( (GNUNET_OK !=
+                TALER_TESTING_get_trait_contract_terms (cmd,
+                                                        0,
+                                                        &contract_terms)) ||
+               (GNUNET_OK !=
+                TALER_TESTING_get_trait_amount_obj (cmd,
+                                                    
TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_VALUE,
+                                                    &deposit_value)) ||
+               (GNUNET_OK !=
+                TALER_TESTING_get_trait_amount_obj (cmd,
+                                                    
TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_FEE,
+                                                    &deposit_fee)) )
+          {
+            GNUNET_break (0);
+            TALER_TESTING_interpreter_fail (pts->is);
+            return;
+          }
+          order_id = json_string_value (json_object_get (contract_terms,
+                                                         "order_id"));
+          if (NULL == order_id)
+          {
+            GNUNET_break (0);
+            TALER_TESTING_interpreter_fail (pts->is);
+            return;
+          }
+          if (0 != strcmp (tdd->order_id,
+                           order_id))
+            continue;
+          if (0 >
+              TALER_amount_add (&sum,
+                                &sum,
+                                deposit_value))
+          {
+            GNUNET_break (0);
+            TALER_TESTING_interpreter_fail (pts->is);
+            return;
+          }
+          if (0 >
+              TALER_amount_add (&fees,
+                                &fees,
+                                deposit_fee))
+          {
+            GNUNET_break (0);
+            TALER_TESTING_interpreter_fail (pts->is);
+            return;
+          }
+        }
+        if (0 !=
+            TALER_amount_cmp (&sum,
+                              &tdd->deposit_value))
+        {
+          GNUNET_break (0);
+          TALER_TESTING_interpreter_fail (pts->is);
+          return;
+        }
+        if (0 !=
+            TALER_amount_cmp (&fees,
+                              &tdd->deposit_fee))
+        {
+          GNUNET_break (0);
+          TALER_TESTING_interpreter_fail (pts->is);
+          return;
+        }
+        GNUNET_assert (0 <=
+                       TALER_amount_add (&total,
+                                         &total,
+                                         &tdd->deposit_value));
+        GNUNET_assert (0 <=
+                       TALER_amount_subtract (&total,
+                                              &total,
+                                              &tdd->deposit_fee));
+      }
+      if (0 !=
+          TALER_amount_cmp (&total,
+                            &pts->credit_amount))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (pts->is);
+        return;
+      }
+      */break;
+    }
+  default:
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (pts->is);
+}
+
+
+/**
+ * Offers information from the POST /transfers CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+post_transfers_traits (void *cls,
+                       const void **ret,
+                       const char *trait,
+                       unsigned int index)
+{
+  struct PostTransfersState *pts = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_wtid (0, &pts->wtid),
+    TALER_TESTING_make_trait_string (0, pts->payto_uri),
+    TALER_TESTING_make_trait_amount_obj (0, &pts->credit_amount),
+    TALER_TESTING_make_trait_string (1, pts->exchange_url),
+    TALER_TESTING_make_trait_absolute_time (0, &pts->execution_time),
+    TALER_TESTING_trait_end (),
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of asking
+ * the bank for the debit transaction history.
+ *
+ * @param cls closure with a `struct PostTransfersState *`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful 
status request
+ *                    0 if the bank's reply is bogus (fails to follow the 
protocol),
+ *                    #MHD_HTTP_NO_CONTENT if there are no more results; on 
success the
+ *                    last callback is always of this status (even if 
`abs(num_results)` were
+ *                    already returned).
+ * @param ec detailed error code
+ * @param serial_id monotonically increasing counter corresponding to the 
transaction
+ * @param details details about the wire transfer
+ * @param json detailed response from the HTTPD, or NULL if reply was not in 
JSON
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
+ */
+static int
+debit_cb (
+  void *cls,
+  unsigned int http_status,
+  enum TALER_ErrorCode ec,
+  uint64_t serial_id,
+  const struct TALER_BANK_DebitDetails *details,
+  const json_t *json)
+{
+  struct PostTransfersState *pts = cls;
+
+  if (MHD_HTTP_NO_CONTENT == http_status)
+  {
+    pts->dhh = NULL;
+    if (! pts->found)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (pts->is);
+      return GNUNET_OK;
+    }
+    GNUNET_assert (NULL != pts->exchange_url);
+    pts->pth = TALER_MERCHANT_transfers_post (pts->is->ctx,
+                                              pts->merchant_url,
+                                              &pts->credit_amount,
+                                              &pts->wtid,
+                                              pts->payto_uri,
+                                              pts->exchange_url,
+                                              &transfers_cb,
+                                              pts);
+    return GNUNET_OK;
+  }
+  if (MHD_HTTP_OK != http_status)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (pts->is);
+    pts->dhh = NULL;
+    return GNUNET_SYSERR;
+  }
+  if (pts->found)
+    return GNUNET_OK;
+  if (0 != TALER_amount_cmp (&pts->credit_amount,
+                             &details->amount))
+    return GNUNET_OK;
+  if ( (NULL != pts->payto_uri) &&
+       (0 != strcasecmp (pts->payto_uri,
+                         details->credit_account_url)) )
+    return GNUNET_OK;
+  pts->found = true;
+  pts->wtid = details->wtid;
+  pts->exchange_url = GNUNET_strdup (details->exchange_base_url);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Run the "POST /transfers" CMD. First, get the bank history to find
+ * the wtid.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_transfers_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct PostTransfersState *pts = cls;
+
+  pts->is = is;
+  pts->dhh = TALER_BANK_debit_history (is->ctx,
+                                       &pts->auth,
+                                       UINT64_MAX,
+                                       -INT64_MAX,
+                                       &debit_cb,
+                                       pts);
+  GNUNET_assert (NULL != pts->dhh);
+}
+
+
+/**
+ * Free the state of a "POST product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_transfers_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct PostTransfersState *pts = cls;
+
+  if (NULL != pts->pth)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "POST /transfers operation did not complete\n");
+    TALER_MERCHANT_transfers_post_cancel (pts->pth);
+  }
+  if (NULL != pts->dhh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "GET debit history operation did not complete\n");
+    TALER_BANK_debit_history_cancel (pts->dhh);
+  }
+  GNUNET_array_grow (pts->deposits,
+                     pts->deposits_length,
+                     0);
+  GNUNET_free (pts->exchange_url);
+  GNUNET_free (pts);
+}
+
+
+/**
+ * Define a POST /transfers CMD.  Details like the WTID and
+ * other required parameters will be extracted from the bank
+ * history, using the latest transfer of the specified
+ * @a credit_amount to the @a merchant_url.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the backend serving the
+ *        "refund increase" request.
+ * @param auth credentials to access the exchange's bank account
+ * @param payto_rui URL of the exchange's bank account
+ * @param credit_amount amount credited
+ * @param http_code expected HTTP response code
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        deposit (commands) we expect to be aggregated in the transfer
+ *        (assuming @a http_code is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_transfer (
+  const char *label,
+  const struct TALER_BANK_AuthenticationData *auth,
+  const char *payto_uri,
+  const char *merchant_url,
+  const char *credit_amount,
+  unsigned int http_code,
+  ...)
+{
+  struct PostTransfersState *pts;
+
+  pts = GNUNET_new (struct PostTransfersState);
+  pts->merchant_url = merchant_url;
+  pts->auth = *auth;
+  pts->payto_uri = payto_uri;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (credit_amount,
+                                         &pts->credit_amount));
+  pts->http_status = http_code;
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_code);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (pts->deposits,
+                           pts->deposits_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = pts,
+      .label = label,
+      .run = &post_transfers_run,
+      .cleanup = &post_transfers_cleanup,
+      .traits = &post_transfers_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_post_transfers.c */
diff --git a/src/lib/testing_api_cmd_refund_increase.c 
b/src/testing/testing_api_cmd_refund_order.c
similarity index 70%
rename from src/lib/testing_api_cmd_refund_increase.c
rename to src/testing/testing_api_cmd_refund_order.c
index da2c70b..ab6f78a 100644
--- a/src/lib/testing_api_cmd_refund_increase.c
+++ b/src/testing/testing_api_cmd_refund_order.c
@@ -17,7 +17,7 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/testing_api_cmd_refund_increase.c
+ * @file lib/testing_api_cmd_refund_order.c
  * @brief command to test refunds.
  * @author Marcello Stanisci
  * @author Christian Grothoff
@@ -32,12 +32,12 @@
 /**
  * State for a "refund increase" CMD.
  */
-struct RefundIncreaseState
+struct RefundState
 {
   /**
-   * Operation handle for a POST /refund request.
+   * Operation handle for a POST /orders/$ID/refund request.
    */
-  struct TALER_MERCHANT_RefundIncreaseOperation *rio;
+  struct TALER_MERCHANT_OrderRefundHandle *orh;
 
   /**
    * Base URL of the merchant serving the request.
@@ -54,11 +54,6 @@ struct RefundIncreaseState
    */
   const char *refund_amount;
 
-  /**
-   * Refund fee.
-   */
-  const char *refund_fee;
-
   /**
    * Human-readable justification for the refund.
    */
@@ -76,29 +71,6 @@ struct RefundIncreaseState
 };
 
 
-/**
- * Free the state of a "refund increase" CMD, and
- * possibly cancel a pending "refund increase" operation.
- *
- * @param cls closure
- * @param cmd command currently being freed.
- */
-static void
-refund_increase_cleanup (void *cls,
-                         const struct TALER_TESTING_Command *cmd)
-{
-  struct RefundIncreaseState *ris = cls;
-
-  if (NULL != ris->rio)
-  {
-    TALER_LOG_WARNING ("Refund-increase operation"
-                       " did not complete\n");
-    TALER_MERCHANT_refund_increase_cancel (ris->rio);
-  }
-  GNUNET_free (ris);
-}
-
-
 /**
  * Process POST /refund (increase) response; just checking
  * if the HTTP response code is the one expected.
@@ -107,12 +79,12 @@ refund_increase_cleanup (void *cls,
  * @param hr HTTP response
  */
 static void
-refund_increase_cb (void *cls,
-                    const struct TALER_MERCHANT_HttpResponse *hr)
+refund_cb (void *cls,
+           const struct TALER_MERCHANT_HttpResponse *hr)
 {
-  struct RefundIncreaseState *ris = cls;
+  struct RefundState *ris = cls;
 
-  ris->rio = NULL;
+  ris->orh = NULL;
   if (ris->http_code != hr->http_status)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -138,21 +110,22 @@ refund_increase_run (void *cls,
                      const struct TALER_TESTING_Command *cmd,
                      struct TALER_TESTING_Interpreter *is)
 {
-  struct RefundIncreaseState *ris = cls;
+  struct RefundState *ris = cls;
   struct TALER_Amount refund_amount;
 
   ris->is = is;
-  if (GNUNET_OK != TALER_string_to_amount (ris->refund_amount,
-                                           &refund_amount))
+  if (GNUNET_OK !=
+      TALER_string_to_amount (ris->refund_amount,
+                              &refund_amount))
     TALER_TESTING_FAIL (is);
-  ris->rio = TALER_MERCHANT_refund_increase (is->ctx,
-                                             ris->merchant_url,
-                                             ris->order_id,
-                                             &refund_amount,
-                                             ris->reason,
-                                             &refund_increase_cb,
-                                             ris);
-  if (NULL == ris->rio)
+  ris->orh = TALER_MERCHANT_post_order_refund (is->ctx,
+                                               ris->merchant_url,
+                                               ris->order_id,
+                                               &refund_amount,
+                                               ris->reason,
+                                               &refund_cb,
+                                               ris);
+  if (NULL == ris->orh)
     TALER_TESTING_FAIL (is);
 }
 
@@ -173,10 +146,12 @@ refund_increase_traits (void *cls,
                         const char *trait,
                         unsigned int index)
 {
-  struct RefundIncreaseState *ris = cls;
+  struct RefundState *ris = cls;
   struct TALER_TESTING_Trait traits[] = {
     TALER_TESTING_make_trait_string (0,
                                      ris->refund_amount),
+    TALER_TESTING_make_trait_string (1,
+                                     ris->reason),
     TALER_TESTING_trait_end ()
   };
 
@@ -188,7 +163,29 @@ refund_increase_traits (void *cls,
 
 
 /**
- * Define a "refund increase" CMD.
+ * Free the state of a "refund increase" CMD, and
+ * possibly cancel a pending "refund increase" operation.
+ *
+ * @param cls closure
+ * @param cmd command currently being freed.
+ */
+static void
+refund_increase_cleanup (void *cls,
+                         const struct TALER_TESTING_Command *cmd)
+{
+  struct RefundState *ris = cls;
+
+  if (NULL != ris->orh)
+  {
+    TALER_LOG_WARNING ("Refund operation did not complete\n");
+    TALER_MERCHANT_post_order_refund_cancel (ris->orh);
+  }
+  GNUNET_free (ris);
+}
+
+
+/**
+ * Define a "refund order" CMD.
  *
  * @param label command label.
  * @param merchant_url base URL of the backend serving the
@@ -196,27 +193,23 @@ refund_increase_traits (void *cls,
  * @param reason refund justification, human-readable.
  * @param order_id order id of the contract to refund.
  * @param refund_amount amount to be refund-increased.
- * @param refund_fee refund fee.
  * @param http_code expected HTTP response code.
- *
  * @return the command.
  */
 struct TALER_TESTING_Command
-TALER_TESTING_cmd_refund_increase (const char *label,
-                                   const char *merchant_url,
-                                   const char *reason,
-                                   const char *order_id,
-                                   const char *refund_amount,
-                                   const char *refund_fee,
-                                   unsigned int http_code)
+TALER_TESTING_cmd_merchant_order_refund (const char *label,
+                                         const char *merchant_url,
+                                         const char *reason,
+                                         const char *order_id,
+                                         const char *refund_amount,
+                                         unsigned int http_code)
 {
-  struct RefundIncreaseState *ris;
+  struct RefundState *ris;
 
-  ris = GNUNET_new (struct RefundIncreaseState);
+  ris = GNUNET_new (struct RefundState);
   ris->merchant_url = merchant_url;
   ris->order_id = order_id;
   ris->refund_amount = refund_amount;
-  ris->refund_fee = refund_fee;
   ris->reason = reason;
   ris->http_code = http_code;
   {
@@ -233,4 +226,4 @@ TALER_TESTING_cmd_refund_increase (const char *label,
 }
 
 
-/* end of testing_api_cmd_refund_increase.c */
+/* end of testing_api_cmd_refund_order.c */
diff --git a/src/lib/testing_api_cmd_tip_authorize.c 
b/src/testing/testing_api_cmd_tip_authorize.c
similarity index 58%
rename from src/lib/testing_api_cmd_tip_authorize.c
rename to src/testing/testing_api_cmd_tip_authorize.c
index 928ec04..83b472a 100644
--- a/src/lib/testing_api_cmd_tip_authorize.c
+++ b/src/testing/testing_api_cmd_tip_authorize.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2018 Taler Systems SA
+  Copyright (C) 2014-2020 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
@@ -46,6 +46,12 @@ struct TipAuthorizeState
    */
   unsigned int http_status;
 
+  /**
+   * Reference to the reserv to authorize the tip
+   * from (if NULL, the merchant decides).
+   */
+  const char *reserve_reference;
+
   /**
    * Human-readable justification for the
    * tip authorization carried on by this CMD.
@@ -55,7 +61,7 @@ struct TipAuthorizeState
   /**
    * Amount that should be authorized for tipping.
    */
-  const char *amount;
+  struct TALER_Amount amount;
 
   /**
    * Expected Taler error code for this CMD.
@@ -80,7 +86,7 @@ struct TipAuthorizeState
   /**
    * Handle to the on-going /tip-authorize request.
    */
-  struct TALER_MERCHANT_TipAuthorizeOperation *tao;
+  struct TALER_MERCHANT_TipAuthorizeHandle *tao;
 
   /**
    * The interpreter state.
@@ -103,7 +109,8 @@ static void
 tip_authorize_cb (void *cls,
                   const struct TALER_MERCHANT_HttpResponse *hr,
                   struct GNUNET_HashCode *tip_id,
-                  const char *taler_tip_uri)
+                  const char *taler_tip_uri,
+                  struct GNUNET_TIME_Absolute expiration)
 {
   struct TipAuthorizeState *tas = cls;
 
@@ -134,6 +141,7 @@ tip_authorize_cb (void *cls,
   {
     tas->tip_uri = strdup (taler_tip_uri);
     tas->tip_id = *tip_id;
+    tas->tip_expiration = expiration;
   }
   TALER_TESTING_interpreter_next (tas->is);
 }
@@ -156,15 +164,21 @@ tip_authorize_traits (void *cls,
                       unsigned int index)
 {
   struct TipAuthorizeState *tas = cls;
-  struct TALER_TESTING_Trait traits[] = {
-    TALER_TESTING_make_trait_tip_id (0, &tas->tip_id),
-    TALER_TESTING_trait_end (),
-  };
-
-  return TALER_TESTING_get_trait (traits,
-                                  ret,
-                                  trait,
-                                  index);
+
+  {
+    struct TALER_TESTING_Trait traits[] = {
+      TALER_TESTING_make_trait_tip_id (0, &tas->tip_id),
+      TALER_TESTING_make_trait_amount_obj (0, &tas->amount),
+      TALER_TESTING_make_trait_string (0, tas->justification),
+      TALER_TESTING_make_trait_absolute_time (0, &tas->tip_expiration),
+      TALER_TESTING_trait_end (),
+    };
+
+    return TALER_TESTING_get_trait (traits,
+                                    ret,
+                                    trait,
+                                    index);
+  }
 }
 
 
@@ -181,21 +195,39 @@ tip_authorize_run (void *cls,
                    struct TALER_TESTING_Interpreter *is)
 {
   struct TipAuthorizeState *tas = cls;
-  struct TALER_Amount amount;
 
   tas->is = is;
-  if (GNUNET_OK != TALER_string_to_amount (tas->amount,
-                                           &amount))
-    TALER_TESTING_FAIL (is);
-
-  tas->tao = TALER_MERCHANT_tip_authorize (is->ctx,
-                                           tas->merchant_url,
-                                           "http://merchant.com/pickup";,
-                                           "http://merchant.com/continue";,
-                                           &amount,
-                                           tas->justification,
-                                           tip_authorize_cb,
-                                           tas);
+  if (NULL == tas->reserve_reference)
+  {
+    tas->tao = TALER_MERCHANT_tip_authorize (is->ctx,
+                                             tas->merchant_url,
+                                             "http://merchant.com/pickup";,
+                                             &tas->amount,
+                                             tas->justification,
+                                             &tip_authorize_cb,
+                                             tas);
+  }
+  else
+  {
+    const struct TALER_TESTING_Command *reserve_cmd;
+    const struct TALER_ReservePublicKeyP *reserve_pub;
+
+    reserve_cmd = TALER_TESTING_interpreter_lookup_command (
+      tas->is,
+      tas->reserve_reference);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_reserve_pub (reserve_cmd,
+                                                        0,
+                                                        &reserve_pub));
+    tas->tao = TALER_MERCHANT_tip_authorize2 (is->ctx,
+                                              tas->merchant_url,
+                                              reserve_pub,
+                                              "http://merchant.com/pickup";,
+                                              &tas->amount,
+                                              tas->justification,
+                                              &tip_authorize_cb,
+                                              tas);
+  }
 
   GNUNET_assert (NULL != tas->tao);
 }
@@ -277,9 +309,65 @@ TALER_TESTING_cmd_tip_authorize_with_ec (const char *label,
   tas = GNUNET_new (struct TipAuthorizeState);
   tas->merchant_url = merchant_url;
   tas->justification = justification;
-  tas->amount = amount;
   tas->http_status = http_status;
   tas->expected_ec = ec;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (amount,
+                                         &tas->amount));
+  {
+    struct TALER_TESTING_Command cmd = {
+      .label = label,
+      .cls = tas,
+      .run = &tip_authorize_run,
+      .cleanup = &tip_authorize_cleanup,
+      .traits = &tip_authorize_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Create a /tip-authorize CMD, specifying the Taler error code
+ * that is expected to be returned by the backend.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ *        serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ *        the reserve from which the tip is going to be gotten.
+ * @param reserve_reference reference to a command that created
+ *        a reserve.
+ * @param http_status the HTTP response code which is expected
+ *        for this operation.
+ * @param justification human-readable justification for this
+ *        tip authorization.
+ * @param amount the amount to authorize for tipping.
+ * @param ec expected Taler-defined error code.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_from_reserve_with_ec (const char *label,
+                                                      const char *merchant_url,
+                                                      const char *exchange_url,
+                                                      const char *
+                                                      reserve_reference,
+                                                      unsigned int http_status,
+                                                      const char 
*justification,
+                                                      const char *amount,
+                                                      enum TALER_ErrorCode ec)
+{
+  struct TipAuthorizeState *tas;
+
+  tas = GNUNET_new (struct TipAuthorizeState);
+  tas->merchant_url = merchant_url;
+  tas->justification = justification;
+  tas->http_status = http_status;
+  tas->expected_ec = ec;
+  tas->reserve_reference = reserve_reference;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (amount,
+                                         &tas->amount));
   {
     struct TALER_TESTING_Command cmd = {
       .label = label,
@@ -321,8 +409,59 @@ TALER_TESTING_cmd_tip_authorize (const char *label,
   tas = GNUNET_new (struct TipAuthorizeState);
   tas->merchant_url = merchant_url;
   tas->justification = justification;
-  tas->amount = amount;
   tas->http_status = http_status;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (amount,
+                                         &tas->amount));
+  {
+    struct TALER_TESTING_Command cmd = {
+      .label = label,
+      .cls = tas,
+      .run = &tip_authorize_run,
+      .cleanup = &tip_authorize_cleanup,
+      .traits = &tip_authorize_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Create a /tip-authorize CMD.
+ *
+ * @param label this command label
+ * @param merchant_url the base URL of the merchant that will
+ *        serve the /tip-authorize request.
+ * @param exchange_url the base URL of the exchange that owns
+ *        the reserve from which the tip is going to be gotten.
+ * @param reserve_reference reference to a command that created
+ *        a reserve.
+ * @param http_status the HTTP response code which is expected
+ *        for this operation.
+ * @param justification human-readable justification for this
+ *        tip authorization.
+ * @param amount the amount to authorize for tipping.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize_from_reserve (const char *label,
+                                              const char *merchant_url,
+                                              const char *exchange_url,
+                                              const char *reserve_refernce,
+                                              unsigned int http_status,
+                                              const char *justification,
+                                              const char *amount)
+{
+  struct TipAuthorizeState *tas;
+
+  tas = GNUNET_new (struct TipAuthorizeState);
+  tas->merchant_url = merchant_url;
+  tas->reserve_reference = reserve_refernce;
+  tas->justification = justification;
+  tas->http_status = http_status;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (amount,
+                                         &tas->amount));
   {
     struct TALER_TESTING_Command cmd = {
       .label = label,
diff --git a/src/lib/testing_api_cmd_tip_pickup.c 
b/src/testing/testing_api_cmd_tip_pickup.c
similarity index 92%
rename from src/lib/testing_api_cmd_tip_pickup.c
rename to src/testing/testing_api_cmd_tip_pickup.c
index dd96352..8f2dacf 100644
--- a/src/lib/testing_api_cmd_tip_pickup.c
+++ b/src/testing/testing_api_cmd_tip_pickup.c
@@ -65,7 +65,7 @@ struct TipPickupState
   /**
    * Handle to a on-going /tip/pickup request.
    */
-  struct TALER_MERCHANT_TipPickupOperation *tpo;
+  struct TALER_MERCHANT_TipPickupHandle *tpo;
 
   /**
    * The interpreter state.
@@ -84,10 +84,15 @@ struct TipPickupState
    */
   struct TALER_Amount *amounts_obj;
 
+  /**
+   * The sum of the the amounts above.
+   */
+  struct TALER_Amount total_amount;
+
   /**
    * How many coins are involved in the tipping operation.
    */
-  unsigned int num_coins;
+  uint64_t num_coins;
 
   /**
    * The array of denomination keys, in the same order of @a
@@ -237,6 +242,15 @@ tip_pickup_run (void *cls,
         GNUNET_assert (GNUNET_OK ==
                        TALER_string_to_amount (tps->amounts[i],
                                                &tps->amounts_obj[i]));
+        if (0 == i)
+          GNUNET_assert (GNUNET_OK ==
+                         TALER_amount_get_zero (tps->amounts_obj[i].currency,
+                                                &tps->total_amount));
+
+        GNUNET_assert (0 <
+                       TALER_amount_add (&tps->total_amount,
+                                         &tps->total_amount,
+                                         &tps->amounts_obj[i]));
         tps->dks[i] = TALER_TESTING_find_pk (is->keys,
                                              &tps->amounts_obj[i]);
         if (NULL == tps->dks[i])
@@ -293,9 +307,9 @@ tip_pickup_cleanup (void *cls,
 {
   struct TipPickupState *tps = cls;
 
-  GNUNET_free_non_null (tps->amounts_obj);
-  GNUNET_free_non_null (tps->dks);
-  GNUNET_free_non_null (tps->psa);
+  GNUNET_free (tps->amounts_obj);
+  GNUNET_free (tps->dks);
+  GNUNET_free (tps->psa);
   if (NULL != tps->sigs)
   {
     for (unsigned int i = 0; i<tps->num_coins; i++)
@@ -329,7 +343,7 @@ tip_pickup_traits (void *cls,
                    unsigned int index)
 {
   struct TipPickupState *tps = cls;
-  #define NUM_TRAITS (tps->num_coins * 5) + 2
+  #define NUM_TRAITS (tps->num_coins * 5) + 4
   struct TALER_TESTING_Trait traits[NUM_TRAITS];
 
   for (unsigned int i = 0; i<tps->num_coins; i++)
@@ -345,6 +359,12 @@ tip_pickup_traits (void *cls,
     traits[i + (tps->num_coins * 4)] =
       TALER_TESTING_make_trait_amount_obj (i, &tps->amounts_obj[i]);
   }
+  traits[NUM_TRAITS - 4]
+    = TALER_TESTING_make_trait_amount_obj (tps->num_coins,
+                                           &tps->total_amount);
+  traits[NUM_TRAITS - 3]
+    = TALER_TESTING_make_trait_uint64 (0,
+                                       &tps->num_coins);
   traits[NUM_TRAITS - 2]
     = TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL,
                                     tps->exchange_url);
diff --git a/src/testing/testing_api_cmd_wallet_get_order.c 
b/src/testing/testing_api_cmd_wallet_get_order.c
new file mode 100644
index 0000000..86072f4
--- /dev/null
+++ b/src/testing/testing_api_cmd_wallet_get_order.c
@@ -0,0 +1,330 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 lib/testing_api_cmd_wallet_get_order.c
+ * @brief command to test GET /order/$ORDER_ID
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a GET /orders/$ORDER_ID CMD.
+ */
+struct WalletGetOrderState
+{
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code for this CMD.
+   */
+  unsigned int http_status;
+
+  /**
+   * The handle to the current GET /orders/$ORDER_ID request.
+   */
+  struct TALER_MERCHANT_OrderWalletGetHandle *ogh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reference to a command that created an order.
+   */
+  const char *order_reference;
+
+  /**
+   * Whether the order was paid or not.
+   */
+  bool paid;
+
+  /**
+   * Whether the order was refunded or not.
+   */
+  bool refunded;
+
+  /**
+   * A NULL-terminated list of refunds associated with this order.
+   */
+  const char **refunds;
+
+  /**
+   * The length of @e refunds.
+   */
+  unsigned int refunds_length;
+};
+
+
+/**
+ * Callback to process a GET /orders/$ID request
+ *
+ * @param cls closure
+ * @param hr HTTP response details
+ * @param paid #GNUNET_YES if the payment is settled, #GNUNET_NO if not
+ *        settled, #GNUNET_SYSERR on error
+ *        (note that refunded payments are returned as paid!)
+ * @param refunded #GNUNET_YES if there is at least on refund on this payment,
+ *        #GNUNET_NO if refunded, #GNUNET_SYSERR or error
+ * @param refunded_amount amount that was refunded, NULL if there
+ *        was no refund
+ * @param taler_pay_uri the URI that instructs the wallets to process
+ *                      the payment
+ * @param already_paid_order_id equivalent order that this customer
+ *                      paid already, or NULL for none
+ * @param merchant_pub public key of the merchant
+ * @param num_refunds length of the @a refunds array
+ * @param refunds details about the refund processing
+ */
+static void
+wallet_get_order_cb (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  enum GNUNET_GenericReturnValue paid,
+  enum GNUNET_GenericReturnValue refunded,
+  struct TALER_Amount *refund_amount,
+  const char *taler_pay_uri,
+  const char *already_paid_order_id,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  unsigned int num_refunds,
+  const struct TALER_MERCHANT_RefundDetail *refunds)
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct WalletGetOrderState *gos = cls;
+  bool paid_b = (paid == GNUNET_YES);
+  bool refunded_b = (refunded == GNUNET_YES);
+
+  gos->ogh = NULL;
+  if (gos->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gos->is));
+    TALER_TESTING_interpreter_fail (gos->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use gos->order_reference here to
+    // check if the data returned matches that from the POST / PATCH
+    if (gos->paid != paid_b)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Order paid does not match\n");
+      TALER_TESTING_interpreter_fail (gos->is);
+      return;
+    }
+    if (gos->refunded != refunded_b)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Order refunded does not match\n");
+      TALER_TESTING_interpreter_fail (gos->is);
+      return;
+    }
+    if (gos->refunds_length != num_refunds)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Order refunds count does not match\n");
+      TALER_TESTING_interpreter_fail (gos->is);
+      return;
+    }
+    for (unsigned int i = 0; i < num_refunds; ++i)
+    {
+      const struct TALER_TESTING_Command *refund_cmd;
+      const char *expected_amount_str;
+      struct TALER_Amount expected_amount;
+
+      refund_cmd = TALER_TESTING_interpreter_lookup_command (
+        gos->is,
+        gos->refunds[i]);
+
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_string (refund_cmd,
+                                          0,
+                                          &expected_amount_str))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Could not fetch refund amount\n");
+        TALER_TESTING_interpreter_fail (gos->is);
+        return;
+      }
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_string_to_amount (expected_amount_str,
+                                             &expected_amount));
+      if ((GNUNET_OK !=
+           TALER_amount_cmp_currency (&expected_amount,
+                                      &refunds[i].refund_amount)) ||
+          (0 != TALER_amount_cmp (&expected_amount,
+                                  &refunds[i].refund_amount)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Refund amounts do not match\n");
+        TALER_TESTING_interpreter_fail (gos->is);
+        return;
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gos->is);
+}
+
+
+/**
+ * Run the "GET order" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+wallet_get_order_run (void *cls,
+                      const struct TALER_TESTING_Command *cmd,
+                      struct TALER_TESTING_Interpreter *is)
+{
+  struct WalletGetOrderState *gos = cls;
+  const struct TALER_TESTING_Command *order_cmd;
+  const char *order_id;
+  const struct GNUNET_HashCode *h_contract;
+
+  order_cmd = TALER_TESTING_interpreter_lookup_command (
+    is,
+    gos->order_reference);
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_order_id (order_cmd,
+                                        0,
+                                        &order_id))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_h_contract_terms (order_cmd,
+                                                0,
+                                                &h_contract))
+    TALER_TESTING_FAIL (is);
+
+  gos->is = is;
+  gos->ogh = TALER_MERCHANT_wallet_order_get (is->ctx,
+                                              gos->merchant_url,
+                                              order_id,
+                                              h_contract,
+                                              GNUNET_TIME_UNIT_ZERO,
+                                              NULL,
+                                              NULL,
+                                              &wallet_get_order_cb,
+                                              gos);
+}
+
+
+/**
+ * Free the state of a "GET order" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+wallet_get_order_cleanup (void *cls,
+                          const struct TALER_TESTING_Command *cmd)
+{
+  struct WalletGetOrderState *gos = cls;
+
+  if (NULL != gos->ogh)
+  {
+    TALER_LOG_WARNING ("Get tip operation did not complete\n");
+    TALER_MERCHANT_wallet_order_get_cancel (gos->ogh);
+  }
+  GNUNET_free (gos);
+}
+
+
+/**
+ * Define a GET /orders/$ORDER_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param order_reference reference to a command that created an order.
+ * @param paid whether the order has been paid for or not.
+ * @param refunded whether the order has been refunded.
+ * @param http_status expected HTTP response code for the request.
+ * @param ... NULL-terminated list of labels (const char *) of
+ *        refunds (commands) we expect to be aggregated in the transfer
+ *        (assuming @a http_code is #MHD_HTTP_OK). If @e refunded is false,
+ *        this parameter is ignored.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_order (const char *label,
+                                    const char *merchant_url,
+                                    const char *order_reference,
+                                    bool paid,
+                                    bool refunded,
+                                    unsigned int http_status,
+                                    ...)
+{
+  struct WalletGetOrderState *gos;
+
+  gos = GNUNET_new (struct WalletGetOrderState);
+  gos->merchant_url = merchant_url;
+  gos->order_reference = order_reference;
+  gos->http_status = http_status;
+  gos->paid = paid;
+  gos->refunded = refunded;
+  gos->refunds_length = 0;
+  if (refunded)
+  {
+    const char *clabel;
+    va_list ap;
+
+    va_start (ap, http_status);
+    while (NULL != (clabel = va_arg (ap, const char *)))
+    {
+      GNUNET_array_append (gos->refunds,
+                           gos->refunds_length,
+                           clabel);
+    }
+    va_end (ap);
+  }
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = gos,
+      .label = label,
+      .run = &wallet_get_order_run,
+      .cleanup = &wallet_get_order_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_wallet_get_order.c */
diff --git a/src/testing/testing_api_cmd_wallet_get_tip.c 
b/src/testing/testing_api_cmd_wallet_get_tip.c
new file mode 100644
index 0000000..7d56b46
--- /dev/null
+++ b/src/testing/testing_api_cmd_wallet_get_tip.c
@@ -0,0 +1,283 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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 lib/testing_api_cmd_wallet_get_tip.c
+ * @brief command to test the tipping.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * State for a GET /tips/$TIP_ID CMD.
+ */
+struct WalletTipGetState
+{
+
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * Expected HTTP response code for this CMD.
+   */
+  unsigned int http_status;
+
+  /**
+   * Whether to compare amounts or not.
+   */
+  bool cmp_amounts;
+
+  /**
+   * The expected amount remaining.
+   */
+  struct TALER_Amount amount_remaining;
+
+  /**
+   * The handle to the current GET /tips/$TIP_ID request.
+   */
+  struct TALER_MERCHANT_TipWalletGetHandle *tgh;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reference to a command that created a tip.
+   */
+  const char *tip_reference;
+};
+
+
+/**
+ * Callback to process a GET /tips/$TIP_ID request, it mainly
+ * checks that what the backend returned matches the command's
+ * expectations.
+ *
+ * @param cls closure
+ * @param hr HTTP response
+ * @param reserve_expiration when the tip reserve will expire
+ * @param exchange_url from where to pick up the tip
+ * @param amount_remaining how much is remaining
+ */
+static void
+wallet_tip_get_cb (void *cls,
+                   const struct TALER_MERCHANT_HttpResponse *hr,
+                   struct GNUNET_TIME_Absolute reserve_expiration,
+                   const char *exchange_url,
+                   const struct TALER_Amount *amount_remaining)
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct WalletTipGetState *gts = cls;
+  const struct TALER_TESTING_Command *tip_cmd;
+
+  tip_cmd = TALER_TESTING_interpreter_lookup_command (
+    gts->is,
+    gts->tip_reference);
+
+  gts->tgh = NULL;
+  if (gts->http_status != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                hr->http_status,
+                (int) hr->ec,
+                TALER_TESTING_interpreter_get_current_label (gts->is));
+    TALER_TESTING_interpreter_fail (gts->is);
+    return;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    // FIXME: use gts->tip_reference here to
+    // check if the data returned matches that from the POST / PATCH
+    if (gts->cmp_amounts)
+    {
+      if ((GNUNET_OK != TALER_amount_cmp_currency (&gts->amount_remaining,
+                                                   amount_remaining)) ||
+          (0 != TALER_amount_cmp (&gts->amount_remaining,
+                                  amount_remaining)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Amount remaining on tip does not match\n");
+        TALER_TESTING_interpreter_fail (gts->is);
+        return;
+      }
+    }
+    {
+      const struct GNUNET_TIME_Absolute *expiration;
+
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_absolute_time (tip_cmd,
+                                                 0,
+                                                 &expiration))
+        TALER_TESTING_interpreter_fail (gts->is);
+      if (expiration->abs_value_us != reserve_expiration.abs_value_us)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Tip expiration does not match\n");
+        TALER_TESTING_interpreter_fail (gts->is);
+        return;
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (gts->is);
+}
+
+
+/**
+ * Run the "GET tip" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+wallet_get_tip_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct WalletTipGetState *tgs = cls;
+  const struct TALER_TESTING_Command *tip_cmd;
+  const struct GNUNET_HashCode *tip_id;
+
+  tip_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                      tgs->tip_reference);
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_tip_id (tip_cmd,
+                                      0,
+                                      &tip_id))
+    TALER_TESTING_FAIL (is);
+
+  tgs->is = is;
+  tgs->tgh = TALER_MERCHANT_wallet_tip_get (is->ctx,
+                                            tgs->merchant_url,
+                                            tip_id,
+                                            &wallet_tip_get_cb,
+                                            tgs);
+}
+
+
+/**
+ * Free the state of a "GET tip" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+wallet_get_tip_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct WalletTipGetState *tgs = cls;
+
+  if (NULL != tgs->tgh)
+  {
+    TALER_LOG_WARNING ("Get tip operation did not complete\n");
+    TALER_MERCHANT_wallet_tip_get_cancel (tgs->tgh);
+  }
+  GNUNET_free (tgs);
+}
+
+
+/**
+ * Define a GET /tips/$TIP_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_tip (const char *label,
+                                  const char *merchant_url,
+                                  const char *tip_reference,
+                                  unsigned int http_status)
+{
+  struct WalletTipGetState *tgs;
+
+  tgs = GNUNET_new (struct WalletTipGetState);
+  tgs->merchant_url = merchant_url;
+  tgs->tip_reference = tip_reference;
+  tgs->http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = tgs,
+      .label = label,
+      .run = &wallet_get_tip_run,
+      .cleanup = &wallet_get_tip_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Define a GET /tips/$TIP_ID CMD.
+ *
+ * @param label the command label
+ * @param merchant_url base URL of the merchant which will
+ *        serve the request.
+ * @param tip_reference reference to a command that created a tip.
+ * @param amount_remaining the balance remaining after pickups.
+ * @param http_status expected HTTP response code for the request.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_get_tip2 (const char *label,
+                                   const char *merchant_url,
+                                   const char *tip_reference,
+                                   const char *amount_remaining,
+                                   unsigned int http_status)
+{
+  struct WalletTipGetState *tgs;
+
+  tgs = GNUNET_new (struct WalletTipGetState);
+  tgs->merchant_url = merchant_url;
+  tgs->tip_reference = tip_reference;
+  tgs->cmp_amounts = true;
+  GNUNET_assert (GNUNET_OK == TALER_string_to_amount (amount_remaining,
+                                                      &tgs->amount_remaining));
+  tgs->http_status = http_status;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = tgs,
+      .label = label,
+      .run = &wallet_get_tip_run,
+      .cleanup = &wallet_get_tip_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_wallet_get_tip.c */
diff --git a/src/lib/testing_api_helpers.c b/src/testing/testing_api_helpers.c
similarity index 90%
rename from src/lib/testing_api_helpers.c
rename to src/testing/testing_api_helpers.c
index 326bfcb..59ca0bd 100644
--- a/src/lib/testing_api_helpers.c
+++ b/src/testing/testing_api_helpers.c
@@ -146,19 +146,18 @@ TALER_TESTING_prepare_merchant (const char 
*config_filename)
   }
 
   /* DB preparation */
-  if (NULL == (dbinit_proc = GNUNET_OS_start_process
-                               (GNUNET_NO,
-                               GNUNET_OS_INHERIT_STD_ALL,
-                               NULL, NULL, NULL,
-                               "taler-merchant-dbinit",
-                               "taler-merchant-dbinit",
-                               "-c", config_filename,
-                               "-r",
-                               NULL)))
+  if (NULL == (dbinit_proc = GNUNET_OS_start_process (
+                 GNUNET_NO,
+                 GNUNET_OS_INHERIT_STD_ALL,
+                 NULL, NULL, NULL,
+                 "taler-merchant-dbinit",
+                 "taler-merchant-dbinit",
+                 "-c", config_filename,
+                 "-r",
+                 NULL)))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failed to run taler-merchant-dbinit."
-                " Check your PATH.\n");
+                "Failed to run taler-merchant-dbinit. Check your PATH.\n");
     MERCHANT_FAIL ();
   }
 
diff --git a/src/lib/testing_api_trait_refund_entry.c 
b/src/testing/testing_api_trait_claim_nonce.c
similarity index 55%
copy from src/lib/testing_api_trait_refund_entry.c
copy to src/testing/testing_api_trait_claim_nonce.c
index 6a27cfb..9649479 100644
--- a/src/lib/testing_api_trait_refund_entry.c
+++ b/src/testing/testing_api_trait_claim_nonce.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2018 Taler Systems SA
+  Copyright (C) 2020 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
@@ -16,63 +16,61 @@
   License along with TALER; see the file COPYING.  If not, see
   <http://www.gnu.org/licenses/>
 */
-
 /**
- * @file lib/testing_api_trait_refund_entry.c
- * @brief command to offer refund entry trait.
- * @author Marcello Stanisci
+ * @file lib/testing_api_trait_claim_nonce.c
+ * @brief offer a trait that is the nonce used to claim an order.
+ * @author Jonathan Buchanan
  */
-
 #include "platform.h"
 #include <taler/taler_signatures.h>
 #include <taler/taler_exchange_service.h>
 #include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
 
-#define TALER_TESTING_TRAIT_REFUND_ENTRY "refund-entry"
+#define TALER_TESTING_TRAIT_CLAIM_NONCE "nonce"
 
 /**
- * Obtain refund entry from a @a cmd.
+ * Obtain an order claim nonce from a @a cmd.
  *
  * @param cmd command to extract the trait from.
- * @param index the trait index.
- * @param refund_entry[out] set to the wanted data.
+ * @param index which nonce to pick if @a
+ *        cmd has multiple on offer
+ * @param nonce[out] set to the wanted data.
  *
  * @return #GNUNET_OK on success
  */
 int
-TALER_TESTING_get_trait_refund_entry
+TALER_TESTING_get_trait_claim_nonce
   (const struct TALER_TESTING_Command *cmd,
   unsigned int index,
-  const struct TALER_MERCHANT_RefundEntry **refund_entry)
+  const struct GNUNET_CRYPTO_EddsaPublicKey **nonce)
 {
   return cmd->traits (cmd->cls,
-                      (const void **) refund_entry,
-                      TALER_TESTING_TRAIT_REFUND_ENTRY,
+                      (const void **) nonce,
+                      TALER_TESTING_TRAIT_CLAIM_NONCE,
                       index);
 }
 
 
 /**
- * Offer refund entry.
- *
- * @param index index number of the trait to offer.
- * @param refund_entry set to the offered refund entry.
+ * Offer an order claim nonce.
  *
+ * @param index which nonce to offer if there are
+ *        multiple on offer.
+ * @param nonce set to the offered nonce.
  * @return the trait
  */
 struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_refund_entry
+TALER_TESTING_make_trait_claim_nonce
   (unsigned int index,
-  const struct TALER_MERCHANT_RefundEntry *refund_entry)
+  const struct GNUNET_CRYPTO_EddsaPublicKey *nonce)
 {
   struct TALER_TESTING_Trait ret = {
     .index = index,
-    .trait_name = TALER_TESTING_TRAIT_REFUND_ENTRY,
-    .ptr = (const void *) refund_entry
+    .trait_name = TALER_TESTING_TRAIT_CLAIM_NONCE,
+    .ptr = (const void *) nonce
   };
   return ret;
 }
 
 
-/* end of testing_api_trait_refund_entry.c */
+/* end of testing_api_trait_claim_nonce.c */
diff --git a/src/lib/testing_api_trait_hash.c 
b/src/testing/testing_api_trait_hash.c
similarity index 100%
rename from src/lib/testing_api_trait_hash.c
rename to src/testing/testing_api_trait_hash.c
diff --git a/src/lib/testing_api_trait_merchant_sig.c 
b/src/testing/testing_api_trait_merchant_sig.c
similarity index 100%
rename from src/lib/testing_api_trait_merchant_sig.c
rename to src/testing/testing_api_trait_merchant_sig.c
diff --git a/src/lib/testing_api_trait_planchet.c 
b/src/testing/testing_api_trait_planchet.c
similarity index 100%
rename from src/lib/testing_api_trait_planchet.c
rename to src/testing/testing_api_trait_planchet.c
diff --git a/src/lib/testing_api_trait_refund_entry.c 
b/src/testing/testing_api_trait_refund_entry.c
similarity index 86%
rename from src/lib/testing_api_trait_refund_entry.c
rename to src/testing/testing_api_trait_refund_entry.c
index 6a27cfb..4181d17 100644
--- a/src/lib/testing_api_trait_refund_entry.c
+++ b/src/testing/testing_api_trait_refund_entry.c
@@ -29,6 +29,8 @@
 #include <taler/taler_testing_lib.h>
 #include "taler_merchant_service.h"
 
+// FIXME: rename: entry->detail!
+
 #define TALER_TESTING_TRAIT_REFUND_ENTRY "refund-entry"
 
 /**
@@ -41,10 +43,10 @@
  * @return #GNUNET_OK on success
  */
 int
-TALER_TESTING_get_trait_refund_entry
-  (const struct TALER_TESTING_Command *cmd,
+TALER_TESTING_get_trait_refund_entry (
+  const struct TALER_TESTING_Command *cmd,
   unsigned int index,
-  const struct TALER_MERCHANT_RefundEntry **refund_entry)
+  const struct TALER_MERCHANT_RefundDetail **refund_entry)
 {
   return cmd->traits (cmd->cls,
                       (const void **) refund_entry,
@@ -62,9 +64,9 @@ TALER_TESTING_get_trait_refund_entry
  * @return the trait
  */
 struct TALER_TESTING_Trait
-TALER_TESTING_make_trait_refund_entry
-  (unsigned int index,
-  const struct TALER_MERCHANT_RefundEntry *refund_entry)
+TALER_TESTING_make_trait_refund_entry (
+  unsigned int index,
+  const struct TALER_MERCHANT_RefundDetail *refund_entry)
 {
   struct TALER_TESTING_Trait ret = {
     .index = index,
diff --git a/src/lib/testing_api_trait_string.c 
b/src/testing/testing_api_trait_string.c
similarity index 100%
rename from src/lib/testing_api_trait_string.c
rename to src/testing/testing_api_trait_string.c
diff --git a/src/lib/tor_merchant.priv b/src/testing/tor_merchant.priv
similarity index 100%
rename from src/lib/tor_merchant.priv
rename to src/testing/tor_merchant.priv

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

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