LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_get-private-orders.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 60.1 % 572 344
Test Date: 2026-09-04 23:42:01 Functions: 78.6 % 14 11

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2019--2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Affero General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file src/backend/taler-merchant-httpd_get-private-orders.c
      18              :  * @brief implement GET /orders
      19              :  * @author Christian Grothoff
      20              :  *
      21              :  * FIXME-cleanup: consider introducing phases / state machine
      22              :  */
      23              : #include "platform.h"
      24              : #include "taler-merchant-httpd_get-private-orders.h"
      25              : #include <taler/taler_merchant_util.h>
      26              : #include <taler/taler_json_lib.h>
      27              : #include <taler/taler_dbevents.h>
      28              : #include "merchant-database/get_contract_terms_status.h"
      29              : #include "merchant-database/get_order.h"
      30              : #include "merchant-database/set_instance.h"
      31              : #include "merchant-database/get_order_status_by_serial.h"
      32              : #include "merchant-database/iterate_orders.h"
      33              : #include "merchant-database/iterate_refunds_detailed.h"
      34              : #include "merchant-database/event_listen.h"
      35              : #include "merchant-database/preflight.h"
      36              : #include "merchant-database/event_notify.h"
      37              : 
      38              : 
      39              : /**
      40              :  * Sensible bound on TALER_MERCHANTDB_OrderFilter.delta
      41              :  */
      42              : #define MAX_DELTA 1024
      43              : 
      44              : #define CSV_HEADER \
      45              :         "Order ID,Row,YYYY-MM-DD,HH:MM,Timestamp,Amount,Refund amount,Pending refund amount,Summary,Refundable,Paid\r\n"
      46              : #define CSV_FOOTER "\r\n"
      47              : 
      48              : #define XML_HEADER "<?xml version=\"1.0\"?>" \
      49              :         "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"" \
      50              :         " xmlns:c=\"urn:schemas-microsoft-com:office:component:spreadsheet\"" \
      51              :         " xmlns:html=\"http://www.w3.org/TR/REC-html40\"" \
      52              :         " xmlns:x2=\"http://schemas.microsoft.com/office/excel/2003/xml\"" \
      53              :         " xmlns:o=\"urn:schemas-microsoft-com:office:office\""        \
      54              :         " xmlns:x=\"urn:schemas-microsoft-com:office:excel\""         \
      55              :         " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">" \
      56              :         "<Styles>" \
      57              :         "<Style ss:ID=\"DateFormat\"><NumberFormat ss:Format=\"yyyy-mm-dd hh:mm:ss\"/></Style>" \
      58              :         "<Style ss:ID=\"Total\"><Font ss:Bold=\"1\"/></Style>" \
      59              :         "</Styles>\n" \
      60              :         "<Worksheet ss:Name=\"Orders\">\n"                            \
      61              :         "<Table>\n" \
      62              :         "<Row>\n" \
      63              :         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Order ID</Data></Cell>\n" \
      64              :         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Timestamp</Data></Cell>\n" \
      65              :         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Price</Data></Cell>\n" \
      66              :         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Refunded</Data></Cell>\n" \
      67              :         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Summary</Data></Cell>\n" \
      68              :         "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Paid</Data></Cell>\n" \
      69              :         "</Row>\n"
      70              : #define XML_FOOTER "</Table></Worksheet></Workbook>"
      71              : 
      72              : 
      73              : /**
      74              :  * A pending GET /orders request.
      75              :  */
      76              : struct TMH_PendingOrder
      77              : {
      78              : 
      79              :   /**
      80              :    * Kept in a DLL.
      81              :    */
      82              :   struct TMH_PendingOrder *prev;
      83              : 
      84              :   /**
      85              :    * Kept in a DLL.
      86              :    */
      87              :   struct TMH_PendingOrder *next;
      88              : 
      89              :   /**
      90              :    * Which connection was suspended.
      91              :    */
      92              :   struct MHD_Connection *con;
      93              : 
      94              :   /**
      95              :    * Which instance is this client polling? This also defines
      96              :    * which DLL this struct is part of.
      97              :    */
      98              :   struct TMH_MerchantInstance *mi;
      99              : 
     100              :   /**
     101              :    * At what time does this request expire? If set in the future, we
     102              :    * may wait this long for a payment to arrive before responding.
     103              :    */
     104              :   struct GNUNET_TIME_Absolute long_poll_timeout;
     105              : 
     106              :   /**
     107              :    * Filter to apply.
     108              :    */
     109              :   struct TALER_MERCHANTDB_OrderFilter of;
     110              : 
     111              :   /**
     112              :    * The array of orders (used for JSON and PDF/Typst).
     113              :    */
     114              :   json_t *pa;
     115              : 
     116              :   /**
     117              :    * Running total of order amounts, for totals row in CSV/XML/PDF.
     118              :    * Initialised to zero on first order seen.
     119              :    */
     120              :   struct TALER_AmountSet total_amount;
     121              : 
     122              :   /**
     123              :    * Running total of granted refund amounts.
     124              :    * Initialised to zero on first paid order seen.
     125              :    */
     126              :   struct TALER_AmountSet total_refund_amount;
     127              : 
     128              :   /**
     129              :    * Running total of pending refund amounts.
     130              :    * Initialised to zero on first paid order seen.
     131              :    */
     132              :   struct TALER_AmountSet total_pending_refund_amount;
     133              : 
     134              :   /**
     135              :    * The name of the instance we are querying for.
     136              :    */
     137              :   const char *instance_id;
     138              : 
     139              :   /**
     140              :    * Alias of @a of.summary_filter, but with memory to be released (owner).
     141              :    */
     142              :   char *summary_filter;
     143              : 
     144              :   /**
     145              :    * The result after adding the orders (#TALER_EC_NONE for okay, anything else for an error).
     146              :    */
     147              :   enum TALER_ErrorCode result;
     148              : 
     149              :   /**
     150              :    * Is the structure in the DLL
     151              :    */
     152              :   bool in_dll;
     153              : 
     154              :   /**
     155              :    * Output format requested by the client.
     156              :    */
     157              :   enum
     158              :   {
     159              :     POF_JSON,
     160              :     POF_CSV,
     161              :     POF_XML,
     162              :     POF_PDF
     163              :   } format;
     164              : 
     165              :   /**
     166              :    * Buffer used when format is #POF_CSV.
     167              :    */
     168              :   struct GNUNET_Buffer csv;
     169              : 
     170              :   /**
     171              :    * Buffer used when format is #POF_XML.
     172              :    */
     173              :   struct GNUNET_Buffer xml;
     174              : 
     175              :   /**
     176              :    * Async context used to run Typst (for #POF_PDF).
     177              :    */
     178              :   struct TALER_MHD_TypstContext *tc;
     179              : 
     180              :   /**
     181              :    * Pre-built MHD response (used when #POF_PDF Typst is done).
     182              :    */
     183              :   struct MHD_Response *response;
     184              : 
     185              :   /**
     186              :    * Task to timeout pending order.
     187              :    */
     188              :   struct GNUNET_SCHEDULER_Task *order_timeout_task;
     189              : 
     190              :   /**
     191              :    * True if a suspended request must run its database query again instead of
     192              :    * replying with the response accumulated so far.
     193              :    */
     194              :   bool recheck;
     195              : 
     196              :   /**
     197              :    * HTTP status to return with @e response.
     198              :    */
     199              :   unsigned int http_status;
     200              : };
     201              : 
     202              : 
     203              : /**
     204              :  * DLL head for requests suspended waiting for Typst.
     205              :  */
     206              : static struct TMH_PendingOrder *pdf_head;
     207              : 
     208              : /**
     209              :  * DLL tail for requests suspended waiting for Typst.
     210              :  */
     211              : static struct TMH_PendingOrder *pdf_tail;
     212              : 
     213              : 
     214              : void
     215          246 : TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi)
     216              : {
     217              :   struct TMH_PendingOrder *po;
     218              : 
     219          246 :   while (NULL != (po = mi->po_head))
     220              :   {
     221            0 :     GNUNET_assert (po->in_dll);
     222            0 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
     223              :                                  mi->po_tail,
     224              :                                  po);
     225            0 :     MHD_resume_connection (po->con);
     226            0 :     po->in_dll = false;
     227              :   }
     228          246 :   if (NULL != mi->po_eh)
     229              :   {
     230            0 :     TALER_MERCHANTDB_event_listen_cancel (mi->po_eh);
     231            0 :     mi->po_eh = NULL;
     232              :   }
     233          246 : }
     234              : 
     235              : 
     236              : void
     237           20 : TMH_force_get_orders_resume_typst ()
     238              : {
     239              :   struct TMH_PendingOrder *po;
     240              : 
     241           20 :   while (NULL != (po = pdf_head))
     242              :   {
     243            0 :     GNUNET_CONTAINER_DLL_remove (pdf_head,
     244              :                                  pdf_tail,
     245              :                                  po);
     246            0 :     MHD_resume_connection (po->con);
     247              :   }
     248           20 : }
     249              : 
     250              : 
     251              : /**
     252              :  * Task run to trigger timeouts on GET /orders requests with long polling.
     253              :  *
     254              :  * @param cls a `struct TMH_PendingOrder *`
     255              :  */
     256              : static void
     257            0 : order_timeout (void *cls)
     258              : {
     259            0 :   struct TMH_PendingOrder *po = cls;
     260            0 :   struct TMH_MerchantInstance *mi = po->mi;
     261              : 
     262            0 :   po->order_timeout_task = NULL;
     263            0 :   po->recheck = true;
     264            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     265              :               "Resuming long polled job due to timeout\n");
     266            0 :   GNUNET_assert (po->in_dll);
     267            0 :   GNUNET_CONTAINER_DLL_remove (mi->po_head,
     268              :                                mi->po_tail,
     269              :                                po);
     270            0 :   po->in_dll = false;
     271            0 :   MHD_resume_connection (po->con);
     272            0 :   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
     273            0 : }
     274              : 
     275              : 
     276              : /**
     277              :  * Cleanup our "context", where we stored the data
     278              :  * we are building for the response.
     279              :  *
     280              :  * @param ctx context to clean up, must be a `struct TMH_PendingOrder *`
     281              :  */
     282              : static void
     283           24 : cleanup (void *ctx)
     284              : {
     285           24 :   struct TMH_PendingOrder *po = ctx;
     286              : 
     287           24 :   if (po->in_dll)
     288              :   {
     289            0 :     struct TMH_MerchantInstance *mi = po->mi;
     290              : 
     291            0 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
     292              :                                  mi->po_tail,
     293              :                                  po);
     294            0 :     MHD_resume_connection (po->con);
     295              :   }
     296           24 :   if (NULL != po->order_timeout_task)
     297              :   {
     298            2 :     GNUNET_SCHEDULER_cancel (po->order_timeout_task);
     299            2 :     po->order_timeout_task = NULL;
     300              :   }
     301           24 :   json_decref (po->pa);
     302           24 :   TALER_amount_set_free (&po->total_amount);
     303           24 :   TALER_amount_set_free (&po->total_refund_amount);
     304           24 :   TALER_amount_set_free (&po->total_pending_refund_amount);
     305           24 :   GNUNET_free (po->summary_filter);
     306           24 :   switch (po->format)
     307              :   {
     308           22 :   case POF_JSON:
     309           22 :     break;
     310            1 :   case POF_CSV:
     311            1 :     GNUNET_buffer_clear (&po->csv);
     312            1 :     break;
     313            1 :   case POF_XML:
     314            1 :     GNUNET_buffer_clear (&po->xml);
     315            1 :     break;
     316            0 :   case POF_PDF:
     317            0 :     if (NULL != po->tc)
     318              :     {
     319            0 :       TALER_MHD_typst_cancel (po->tc);
     320            0 :       po->tc = NULL;
     321              :     }
     322            0 :     break;
     323              :   }
     324           24 :   if (NULL != po->response)
     325              :   {
     326            0 :     MHD_destroy_response (po->response);
     327            0 :     po->response = NULL;
     328              :   }
     329           24 :   GNUNET_free (po);
     330           24 : }
     331              : 
     332              : 
     333              : /**
     334              :  * Closure for #process_refunds_cb().
     335              :  */
     336              : struct ProcessRefundsClosure
     337              : {
     338              :   /**
     339              :    * Place where we accumulate the granted refunds.
     340              :    */
     341              :   struct TALER_Amount total_refund_amount;
     342              : 
     343              :   /**
     344              :    * Place where we accumulate the pending refunds.
     345              :    */
     346              :   struct TALER_Amount pending_refund_amount;
     347              : 
     348              :   /**
     349              :    * Set to an error code if something goes wrong.
     350              :    */
     351              :   enum TALER_ErrorCode ec;
     352              : };
     353              : 
     354              : 
     355              : /**
     356              :  * Function called with information about a refund.
     357              :  * It is responsible for summing up the refund amount.
     358              :  *
     359              :  * @param cls closure
     360              :  * @param refund_serial unique serial number of the refund
     361              :  * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
     362              :  * @param coin_pub public coin from which the refund comes from
     363              :  * @param exchange_url URL of the exchange that issued @a coin_pub
     364              :  * @param rtransaction_id identificator of the refund
     365              :  * @param reason human-readable explanation of the refund
     366              :  * @param refund_amount refund amount which is being taken from @a coin_pub
     367              :  * @param pending true if the this refund was not yet processed by the wallet/exchange
     368              :  */
     369              : static void
     370            0 : process_refunds_cb (void *cls,
     371              :                     uint64_t refund_serial,
     372              :                     struct GNUNET_TIME_Timestamp timestamp,
     373              :                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
     374              :                     const char *exchange_url,
     375              :                     uint64_t rtransaction_id,
     376              :                     const char *reason,
     377              :                     const struct TALER_Amount *refund_amount,
     378              :                     bool pending)
     379              : {
     380            0 :   struct ProcessRefundsClosure *prc = cls;
     381              : 
     382            0 :   if (GNUNET_OK !=
     383            0 :       TALER_amount_cmp_currency (&prc->total_refund_amount,
     384              :                                  refund_amount))
     385              :   {
     386              :     /* Database error, refunds in mixed currency in DB. Not OK! */
     387            0 :     prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE;
     388            0 :     GNUNET_break (0);
     389            0 :     return;
     390              :   }
     391            0 :   GNUNET_assert (0 <=
     392              :                  TALER_amount_add (&prc->total_refund_amount,
     393              :                                    &prc->total_refund_amount,
     394              :                                    refund_amount));
     395            0 :   if (pending)
     396            0 :     GNUNET_assert (0 <=
     397              :                    TALER_amount_add (&prc->pending_refund_amount,
     398              :                                      &prc->pending_refund_amount,
     399              :                                      refund_amount));
     400              : }
     401              : 
     402              : 
     403              : /**
     404              :  * Add one order entry to the running order-amount total in @a po.
     405              :  * Sets po->result to TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT on overflow.
     406              :  *
     407              :  * @param[in,out] po pending order accumulator
     408              :  * @param amount the order amount to add
     409              :  */
     410              : static void
     411           12 : accumulate_total (struct TMH_PendingOrder *po,
     412              :                   const struct TALER_Amount *amount)
     413              : {
     414           12 :   if (0 > TALER_amount_set_add (&po->total_amount,
     415              :                                 amount,
     416              :                                 NULL))
     417              :   {
     418            0 :     GNUNET_break (0);
     419            0 :     po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT;
     420              :   }
     421           12 : }
     422              : 
     423              : 
     424              : /**
     425              :  * Add refund amounts to the running refund totals in @a po.
     426              :  * Sets po->result to TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT on overflow.
     427              :  * Only called for paid orders (where refund tracking is meaningful).
     428              :  *
     429              :  * @param[in,out] po pending order accumulator
     430              :  * @param refund granted refund amount for this order
     431              :  * @param pending pending (not-yet-processed) refund amount for this order
     432              :  */
     433              : static void
     434           12 : accumulate_refund_totals (struct TMH_PendingOrder *po,
     435              :                           const struct TALER_Amount *refund,
     436              :                           const struct TALER_Amount *pending)
     437              : {
     438           12 :   if (TALER_EC_NONE != po->result)
     439            0 :     return;
     440           12 :   if (0 > TALER_amount_set_add (&po->total_refund_amount,
     441              :                                 refund,
     442              :                                 NULL))
     443              :   {
     444            0 :     GNUNET_break (0);
     445            0 :     po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT;
     446            0 :     return;
     447              :   }
     448           12 :   if (0 > TALER_amount_set_add (&po->total_pending_refund_amount,
     449              :                                 pending,
     450              :                                 NULL))
     451              :   {
     452            0 :     GNUNET_break (0);
     453            0 :     po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT;
     454              :   }
     455              : }
     456              : 
     457              : 
     458              : /**
     459              :  * Add order details to our response accumulator.
     460              :  *
     461              :  * @param cls some closure
     462              :  * @param orig_order_id the order this is about
     463              :  * @param order_serial serial ID of the order
     464              :  * @param creation_time when was the order created
     465              :  */
     466              : static void
     467           26 : add_order (void *cls,
     468              :            const char *orig_order_id,
     469              :            uint64_t order_serial,
     470              :            struct GNUNET_TIME_Timestamp creation_time)
     471              : {
     472           26 :   struct TMH_PendingOrder *po = cls;
     473           26 :   json_t *terms = NULL;
     474              :   struct TALER_PrivateContractHashP h_contract_terms;
     475              :   enum GNUNET_DB_QueryStatus qs;
     476           26 :   char *order_id = NULL;
     477           26 :   bool refundable = false;
     478              :   bool paid;
     479              :   bool wired;
     480           26 :   struct TALER_MERCHANT_Contract *contract = NULL;
     481           26 :   struct TALER_MERCHANT_Order *order = NULL;
     482           26 :   int16_t choice_index = -1;
     483           26 :   struct ProcessRefundsClosure prc = {
     484              :     .ec = TALER_EC_NONE
     485              :   };
     486              :   const struct TALER_Amount *amount;
     487              :   char amount_buf[128];
     488              :   char refund_buf[128];
     489              :   char pending_buf[128];
     490           26 :   const struct TALER_MERCHANT_ContractBaseTerms *ct = NULL;
     491              : 
     492              :   /* Bail early if we already have an error */
     493           26 :   if (TALER_EC_NONE != po->result)
     494            2 :     return;
     495              : 
     496           26 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     497              :               "Adding order `%s' (%llu) to result set at instance `%s'\n",
     498              :               orig_order_id,
     499              :               (unsigned long long) order_serial,
     500              :               po->instance_id);
     501           26 :   qs = TALER_MERCHANTDB_set_instance (
     502              :     TMH_db,
     503              :     po->instance_id);
     504           26 :   if (qs <= 0)
     505              :   {
     506            0 :     GNUNET_break (0);
     507            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     508            0 :     return;
     509              :   }
     510           26 :   qs = TALER_MERCHANTDB_get_order_status_by_serial (TMH_db,
     511              :                                                     po->instance_id,
     512              :                                                     order_serial,
     513              :                                                     &order_id,
     514              :                                                     &h_contract_terms,
     515              :                                                     &paid);
     516           26 :   if (qs < 0)
     517              :   {
     518            0 :     GNUNET_break (0);
     519            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     520            0 :     goto cleanup;
     521              :   }
     522           26 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     523              :   {
     524              :     /* Contract terms don't exist, so the order cannot be paid. */
     525            7 :     paid = false;
     526            7 :     if (NULL == orig_order_id)
     527              :     {
     528              :       /* Got a DB trigger about a new proposal, but it
     529              :          was already deleted again. Just ignore the event. */
     530            2 :       GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     531              :                     TALER_MERCHANTDB_set_instance (
     532              :                       TMH_db,
     533              :                       NULL));
     534            2 :       return;
     535              :     }
     536            5 :     order_id = GNUNET_strdup (orig_order_id);
     537              :   }
     538              : 
     539              :   {
     540              :     /* First try to find the order in the contracts */
     541              :     uint64_t os;
     542              :     bool session_matches;
     543              : 
     544           24 :     qs = TALER_MERCHANTDB_get_contract_terms_status (TMH_db,
     545              :                                                      po->instance_id,
     546              :                                                      order_id,
     547              :                                                      NULL,
     548              :                                                      &terms,
     549              :                                                      &os,
     550              :                                                      &paid,
     551              :                                                      &wired,
     552              :                                                      &session_matches,
     553              :                                                      NULL,
     554              :                                                      &choice_index);
     555           24 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     556              :     {
     557           19 :       GNUNET_break (os == order_serial);
     558           19 :       contract = TALER_MERCHANT_contract_parse (terms);
     559           19 :       if (NULL == contract)
     560              :       {
     561            0 :         GNUNET_break (0);
     562            0 :         po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
     563            0 :         goto cleanup;
     564              :       }
     565           19 :       ct = contract->pc->base;
     566              :     }
     567              :   }
     568           24 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     569              :   {
     570              :     /* Might still be unclaimed, so try order table */
     571              :     struct TALER_MerchantPostDataHashP unused;
     572              : 
     573            5 :     paid = false;
     574            5 :     wired = false;
     575            5 :     qs = TALER_MERCHANTDB_get_order (TMH_db,
     576              :                                      po->instance_id,
     577              :                                      order_id,
     578              :                                      NULL,
     579              :                                      &unused,
     580              :                                      &terms);
     581            5 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     582              :     {
     583            5 :       order = TALER_MERCHANT_order_parse (terms);
     584            5 :       if (NULL == order)
     585              :       {
     586            0 :         GNUNET_break (0);
     587            0 :         po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID;
     588            0 :         goto cleanup;
     589              :       }
     590            5 :       ct = order->base;
     591              :     }
     592              :   }
     593           24 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     594              :   {
     595            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     596              :                 "Order %llu disappeared during iteration. Skipping.\n",
     597              :                 (unsigned long long) order_serial);
     598            0 :     goto cleanup;
     599              :   }
     600           24 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     601              :   {
     602            0 :     GNUNET_break (0);
     603            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     604            0 :     goto cleanup;
     605              :   }
     606              : 
     607              : 
     608           24 :   if (paid)
     609              :   {
     610              :     const struct TALER_Amount *brutto;
     611              : 
     612           12 :     GNUNET_assert (NULL != contract);
     613           12 :     switch (ct->version)
     614              :     {
     615           12 :     case TALER_MERCHANT_CONTRACT_VERSION_0:
     616           12 :       brutto = &contract->pc->details.v0.brutto;
     617           12 :       break;
     618            0 :     case TALER_MERCHANT_CONTRACT_VERSION_1:
     619              :       {
     620              :         struct TALER_MERCHANT_ContractChoice *choice;
     621              : 
     622            0 :         if (-1 == choice_index)
     623            0 :           choice_index = 0; /* default choice */
     624            0 :         GNUNET_assert (choice_index < contract->pc->details.v1.choices_len);
     625            0 :         choice = &contract->pc->details.v1.choices[choice_index];
     626            0 :         brutto = &choice->amount;
     627              :       }
     628            0 :       break;
     629            0 :     default:
     630            0 :       GNUNET_break (0);
     631            0 :       goto cleanup;
     632              :     }
     633           12 :     GNUNET_assert (GNUNET_OK ==
     634              :                    TALER_amount_set_zero (brutto->currency,
     635              :                                           &prc.total_refund_amount));
     636           12 :     GNUNET_assert (GNUNET_OK ==
     637              :                    TALER_amount_set_zero (brutto->currency,
     638              :                                           &prc.pending_refund_amount));
     639              : 
     640           12 :     qs = TALER_MERCHANTDB_iterate_refunds_detailed (TMH_db,
     641              :                                                     po->instance_id,
     642              :                                                     &h_contract_terms,
     643              :                                                     &process_refunds_cb,
     644              :                                                     &prc);
     645           12 :     if (0 > qs)
     646              :     {
     647            0 :       GNUNET_break (0);
     648            0 :       po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
     649            0 :       goto cleanup;
     650              :     }
     651           12 :     if (TALER_EC_NONE != prc.ec)
     652              :     {
     653            0 :       GNUNET_break (0);
     654            0 :       po->result = prc.ec;
     655            0 :       goto cleanup;
     656              :     }
     657           12 :     if (0 > TALER_amount_cmp (&prc.total_refund_amount,
     658           12 :                               brutto) &&
     659           12 :         GNUNET_TIME_absolute_is_future (
     660           12 :           contract->pc->refund_deadline.abs_time))
     661            3 :       refundable = true;
     662              :   }
     663              : 
     664              :   /* compute amount totals */
     665           24 :   amount = NULL;
     666           24 :   switch (ct->version)
     667              :   {
     668           24 :   case TALER_MERCHANT_CONTRACT_VERSION_0:
     669              :     {
     670           24 :       amount = (NULL != contract)
     671           19 :         ? &contract->pc->details.v0.brutto
     672              :         : &order->details.v0.brutto;
     673              : 
     674           24 :       if (TALER_amount_is_zero (amount) &&
     675            0 :           (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
     676              :       {
     677              :         /* If we are actually filtering by wire status,
     678              :            and the order was over an amount of zero,
     679              :            do not return it as wire status is not
     680              :            exactly meaningful for orders over zero. */
     681            0 :         goto cleanup;
     682              :       }
     683              : 
     684              :       /* Accumulate order total */
     685           24 :       if (paid)
     686           12 :         accumulate_total (po,
     687              :                           amount);
     688           24 :       if (TALER_EC_NONE != po->result)
     689            0 :         goto cleanup;
     690              :       /* Accumulate refund totals (only meaningful for paid orders) */
     691           24 :       if (paid)
     692              :       {
     693           12 :         accumulate_refund_totals (po,
     694              :                                   &prc.total_refund_amount,
     695              :                                   &prc.pending_refund_amount);
     696           12 :         if (TALER_EC_NONE != po->result)
     697            0 :           goto cleanup;
     698              :       }
     699              :     }
     700           24 :     break;
     701            0 :   case TALER_MERCHANT_CONTRACT_VERSION_1:
     702            0 :     if (-1 == choice_index)
     703            0 :       choice_index = 0; /* default choice */
     704            0 :     if (NULL != contract)
     705              :     {
     706            0 :       struct TALER_MERCHANT_ContractChoice *choice
     707            0 :         = &contract->pc->details.v1.choices[choice_index];
     708              : 
     709            0 :       GNUNET_assert (choice_index < contract->pc->details.v1.choices_len);
     710            0 :       amount = &choice->amount;
     711              :       /* Accumulate order total */
     712            0 :       if (paid)
     713            0 :         accumulate_total (po,
     714              :                           amount);
     715            0 :       if (TALER_EC_NONE != po->result)
     716            0 :         goto cleanup;
     717              :       /* Accumulate refund totals (only meaningful for paid orders) */
     718            0 :       if (paid)
     719              :       {
     720            0 :         accumulate_refund_totals (po,
     721              :                                   &prc.total_refund_amount,
     722              :                                   &prc.pending_refund_amount);
     723            0 :         if (TALER_EC_NONE != po->result)
     724            0 :           goto cleanup;
     725              :       }
     726              :     }
     727              :     else
     728              :     {
     729            0 :       GNUNET_assert (choice_index < order->details.v1.choices_len);
     730            0 :       amount = &order->details.v1.choices[choice_index].amount;
     731            0 :       if (TALER_EC_NONE != po->result)
     732            0 :         goto cleanup;
     733              :     }
     734            0 :     break;
     735            0 :   default:
     736            0 :     GNUNET_break (0);
     737            0 :     po->result = TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION;
     738            0 :     goto cleanup;
     739              :   }
     740              : 
     741              :   /* convert amounts to strings (needed for some formats) */
     742              :   /* FIXME: use currency formatting rules in the future
     743              :      instead of TALER_amount2s for human readability... */
     744           24 :   strcpy (amount_buf,
     745              :           TALER_amount2s (amount));
     746           24 :   if (paid)
     747           12 :     strcpy (refund_buf,
     748              :             TALER_amount2s (&prc.total_refund_amount));
     749           24 :   if (paid)
     750           12 :     strcpy (pending_buf,
     751              :             TALER_amount2s (&prc.pending_refund_amount));
     752              : 
     753           24 :   switch (po->format)
     754              :   {
     755           18 :   case POF_JSON:
     756              :   case POF_PDF:
     757           18 :     GNUNET_assert (
     758              :       0 ==
     759              :       json_array_append_new (
     760              :         po->pa,
     761              :         GNUNET_JSON_PACK (
     762              :           GNUNET_JSON_pack_string ("order_id",
     763              :                                    order_id),
     764              :           GNUNET_JSON_pack_uint64 ("row_id",
     765              :                                    order_serial),
     766              :           GNUNET_JSON_pack_timestamp ("timestamp",
     767              :                                       creation_time),
     768              :           GNUNET_JSON_pack_timestamp (
     769              :             "pay_deadline",
     770              :             (NULL != contract)
     771              :               ? contract->pc->pay_deadline
     772              :               : order->pay_deadline),
     773              :           TALER_JSON_pack_amount ("amount",
     774              :                                   amount),
     775              :           GNUNET_JSON_pack_allow_null (
     776              :             TALER_JSON_pack_amount (
     777              :               "refund_amount",
     778              :               paid
     779              :                 ? &prc.total_refund_amount
     780              :                 : NULL)),
     781              :           GNUNET_JSON_pack_allow_null (
     782              :             TALER_JSON_pack_amount (
     783              :               "pending_refund_amount",
     784              :               paid
     785              :                 ? &prc.pending_refund_amount
     786              :                 : NULL)),
     787              :           GNUNET_JSON_pack_string ("summary",
     788              :                                    ct->summary),
     789              :           GNUNET_JSON_pack_bool ("refundable",
     790              :                                  refundable),
     791              :           GNUNET_JSON_pack_bool ("paid",
     792              :                                  paid),
     793              :           GNUNET_JSON_pack_bool ("wired",
     794              :                                  wired))));
     795           18 :     break;
     796            3 :   case POF_CSV:
     797              :     {
     798            3 :       size_t len = strlen (ct->summary);
     799            3 :       size_t wpos = 0;
     800              :       char *esummary;
     801              :       struct tm *tm;
     802              :       time_t t;
     803              : 
     804              :       /* Escape 'summary' to double '"' as per RFC 4180, 2.7. */
     805            3 :       esummary = GNUNET_malloc (2 * len + 1);
     806           18 :       for (size_t off = 0; off<len; off++)
     807              :       {
     808           15 :         if ('"' == ct->summary[off])
     809            0 :           esummary[wpos++] = '"';
     810           15 :         esummary[wpos++] = ct->summary[off];
     811              :       }
     812            3 :       t = GNUNET_TIME_timestamp_to_s (creation_time);
     813            3 :       tm = localtime (&t);
     814            9 :       GNUNET_buffer_write_fstr (
     815              :         &po->csv,
     816              :         "%s,%llu,%04u-%02u-%02u,%02u:%02u (%s),%llu,%s,%s,%s,\"%s\",%s,%s\r\n",
     817              :         order_id,
     818              :         (unsigned long long) order_serial,
     819            3 :         tm->tm_year + 1900,
     820            3 :         tm->tm_mon + 1,
     821              :         tm->tm_mday,
     822              :         tm->tm_hour,
     823              :         tm->tm_min,
     824              :         tm->tm_zone,
     825              :         (unsigned long long) t,
     826              :         amount_buf,
     827            3 :         paid ? refund_buf : "",
     828            3 :         paid ? pending_buf : "",
     829              :         esummary,
     830              :         refundable ? "yes" : "no",
     831            3 :         paid ? "yes" : "no");
     832            3 :       GNUNET_free (esummary);
     833            3 :       break;
     834              :     }
     835            3 :   case POF_XML:
     836              :     {
     837            3 :       char *esummary = TALER_escape_xml (ct->summary);
     838              :       char creation_time_s[128];
     839              :       const struct tm *tm;
     840              :       time_t tt;
     841              : 
     842            3 :       tt = (time_t) GNUNET_TIME_timestamp_to_s (creation_time);
     843            3 :       tm = gmtime (&tt);
     844            3 :       strftime (creation_time_s,
     845              :                 sizeof (creation_time_s),
     846              :                 "%Y-%m-%dT%H:%M:%S",
     847              :                 tm);
     848            9 :       GNUNET_buffer_write_fstr (
     849              :         &po->xml,
     850              :         "<Row>"
     851              :         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
     852              :         "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"DateTime\">%s</Data></Cell>"
     853              :         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
     854              :         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
     855              :         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
     856              :         "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>"
     857              :         "</Row>\n",
     858              :         order_id,
     859              :         creation_time_s,
     860              :         amount_buf,
     861            3 :         paid ? refund_buf : "",
     862              :         NULL != esummary ? esummary : "",
     863            3 :         paid ? "TRUE" : "FALSE",
     864            3 :         paid ? "1" : "0");
     865            3 :       GNUNET_free (esummary);
     866              :     }
     867            3 :     break;
     868              :   }    /* end switch po->format */
     869              : 
     870           24 : cleanup:
     871           24 :   GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     872              :                 TALER_MERCHANTDB_set_instance (
     873              :                   TMH_db,
     874              :                   NULL));
     875           24 :   json_decref (terms);
     876           24 :   GNUNET_free (order_id);
     877           24 :   if (NULL != order)
     878              :   {
     879            5 :     TALER_MERCHANT_order_free (order);
     880            5 :     order = NULL;
     881              :   }
     882           24 :   if (NULL != contract)
     883              :   {
     884           19 :     TALER_MERCHANT_contract_free (contract);
     885           19 :     contract = NULL;
     886              :   }
     887              : }
     888              : 
     889              : 
     890              : /**
     891              :  * We have received a trigger from the database
     892              :  * that we should (possibly) resume some requests.
     893              :  *
     894              :  * @param cls a `struct TMH_MerchantInstance`
     895              :  * @param extra a `struct TMH_OrderChangeEventP`
     896              :  * @param extra_size number of bytes in @a extra
     897              :  */
     898              : static void
     899            2 : resume_by_event (void *cls,
     900              :                  const void *extra,
     901              :                  size_t extra_size)
     902              : {
     903            2 :   struct TMH_MerchantInstance *mi = cls;
     904            2 :   const struct TMH_OrderChangeEventDetailsP *oce = extra;
     905              :   struct TMH_PendingOrder *pn;
     906              :   enum TMH_OrderStateFlags osf;
     907              :   uint64_t order_serial_id;
     908              :   struct GNUNET_TIME_Timestamp date;
     909              : 
     910            2 :   if (sizeof (*oce) != extra_size)
     911              :   {
     912            0 :     GNUNET_break (0);
     913            0 :     return;
     914              :   }
     915            2 :   osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
     916            2 :   order_serial_id = GNUNET_ntohll (oce->order_serial_id);
     917            2 :   date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
     918            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     919              :               "Received notification about order %llu\n",
     920              :               (unsigned long long) order_serial_id);
     921            2 :   for (struct TMH_PendingOrder *po = mi->po_head;
     922            4 :        NULL != po;
     923            2 :        po = pn)
     924              :   {
     925            2 :     pn = po->next;
     926            4 :     if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
     927            2 :                (0 != (osf & TMH_OSF_PAID))) ||
     928            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
     929            2 :             ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
     930            2 :                (0 != (osf & TMH_OSF_REFUNDED))) ||
     931            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
     932            2 :             ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
     933            2 :                (0 != (osf & TMH_OSF_WIRED))) ||
     934            0 :               (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
     935              :     {
     936            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     937              :                   "Client %p waits on different order type\n",
     938              :                   po);
     939            0 :       continue;
     940              :     }
     941            2 :     if (po->of.delta > 0)
     942              :     {
     943            2 :       if (order_serial_id < po->of.start_row)
     944              :       {
     945            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     946              :                     "Client %p waits on different order row\n",
     947              :                     po);
     948            0 :         continue;
     949              :       }
     950            2 :       if (GNUNET_TIME_timestamp_cmp (date,
     951              :                                      <,
     952              :                                      po->of.date))
     953              :       {
     954            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     955              :                     "Client %p waits on different order date\n",
     956              :                     po);
     957            0 :         continue;
     958              :       }
     959            2 :       if (TALER_EXCHANGE_YNA_ALL == po->of.expired)
     960            2 :         po->of.delta--;
     961              :     }
     962              :     else
     963              :     {
     964            0 :       if (order_serial_id > po->of.start_row)
     965              :       {
     966            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     967              :                     "Client %p waits on different order row\n",
     968              :                     po);
     969            0 :         continue;
     970              :       }
     971            0 :       if (GNUNET_TIME_timestamp_cmp (date,
     972              :                                      >,
     973              :                                      po->of.date))
     974              :       {
     975            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     976              :                     "Client %p waits on different order date\n",
     977              :                     po);
     978            0 :         continue;
     979              :       }
     980            0 :       if (TALER_EXCHANGE_YNA_ALL == po->of.expired)
     981            0 :         po->of.delta++;
     982              :     }
     983            2 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     984              :                 "Waking up client %p!\n",
     985              :                 po);
     986            2 :     if (TALER_EXCHANGE_YNA_ALL != po->of.expired)
     987              :     {
     988              :       /* The event flags do not carry a payment deadline.  Re-run the query
     989              :          so the expiration predicate is evaluated by the database. */
     990            0 :       po->recheck = true;
     991              :     }
     992              :     else
     993              :     {
     994            2 :       add_order (po,
     995              :                  NULL,
     996              :                  order_serial_id,
     997              :                  date);
     998              :     }
     999            2 :     GNUNET_assert (po->in_dll);
    1000            2 :     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    1001              :                                  mi->po_tail,
    1002              :                                  po);
    1003            2 :     po->in_dll = false;
    1004            2 :     MHD_resume_connection (po->con);
    1005            2 :     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    1006              :   }
    1007            2 :   if (NULL == mi->po_head)
    1008              :   {
    1009            2 :     TALER_MERCHANTDB_event_listen_cancel (mi->po_eh);
    1010            2 :     mi->po_eh = NULL;
    1011              :   }
    1012              : }
    1013              : 
    1014              : 
    1015              : /**
    1016              :  * There has been a change or addition of a new @a order_id.  Wake up
    1017              :  * long-polling clients that may have been waiting for this event.
    1018              :  *
    1019              :  * @param mi the instance where the order changed
    1020              :  * @param osf order state flags
    1021              :  * @param date execution date of the order
    1022              :  * @param order_serial_id serial ID of the order in the database
    1023              :  */
    1024              : void
    1025          242 : TMH_notify_order_change (struct TMH_MerchantInstance *mi,
    1026              :                          enum TMH_OrderStateFlags osf,
    1027              :                          struct GNUNET_TIME_Timestamp date,
    1028              :                          uint64_t order_serial_id)
    1029              : {
    1030          484 :   struct TMH_OrderChangeEventDetailsP oce = {
    1031          242 :     .order_serial_id = GNUNET_htonll (order_serial_id),
    1032          242 :     .execution_date = GNUNET_TIME_timestamp_hton (date),
    1033          242 :     .order_state = htonl (osf)
    1034              :   };
    1035          242 :   struct TMH_OrderChangeEventP eh = {
    1036          242 :     .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
    1037          242 :     .header.size = htons (sizeof (eh)),
    1038              :     .merchant_pub = mi->merchant_pub
    1039              :   };
    1040              : 
    1041          242 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1042              :               "Notifying clients of new order %llu at %s\n",
    1043              :               (unsigned long long) order_serial_id,
    1044              :               TALER_B2S (&mi->merchant_pub));
    1045          242 :   TALER_MERCHANTDB_event_notify (TMH_db,
    1046              :                                  &eh.header,
    1047              :                                  &oce,
    1048              :                                  sizeof (oce));
    1049          242 : }
    1050              : 
    1051              : 
    1052              : /**
    1053              :  * Transforms an (untrusted) input filter into a Postgresql LIKE filter.
    1054              :  * Escapes "%" and "_" in the @a input and adds "%" at the beginning
    1055              :  * and the end to turn the @a input into a suitable Postgresql argument.
    1056              :  *
    1057              :  * @param input text to turn into a substring match expression, or NULL
    1058              :  * @return NULL if @a input was NULL, otherwise transformed @a input
    1059              :  */
    1060              : static char *
    1061           24 : tr (const char *input)
    1062              : {
    1063              :   char *out;
    1064              :   size_t slen;
    1065              :   size_t wpos;
    1066              : 
    1067           24 :   if (NULL == input)
    1068           24 :     return NULL;
    1069            0 :   slen = strlen (input);
    1070            0 :   out = GNUNET_malloc (slen * 2 + 3);
    1071            0 :   wpos = 0;
    1072            0 :   out[wpos++] = '%';
    1073            0 :   for (size_t i = 0; i<slen; i++)
    1074              :   {
    1075            0 :     char c = input[i];
    1076              : 
    1077            0 :     if ( (c == '%') ||
    1078              :          (c == '_') )
    1079            0 :       out[wpos++] = '\\';
    1080            0 :     out[wpos++] = c;
    1081              :   }
    1082            0 :   out[wpos++] = '%';
    1083            0 :   GNUNET_assert (wpos < slen * 2 + 3);
    1084            0 :   return out;
    1085              : }
    1086              : 
    1087              : 
    1088              : /**
    1089              :  * Function called with the result of a #TALER_MHD_typst() operation.
    1090              :  *
    1091              :  * @param cls closure, a `struct TMH_PendingOrder *`
    1092              :  * @param tr result of the operation
    1093              :  */
    1094              : static void
    1095            0 : pdf_cb (void *cls,
    1096              :         const struct TALER_MHD_TypstResponse *tr)
    1097              : {
    1098            0 :   struct TMH_PendingOrder *po = cls;
    1099              : 
    1100            0 :   po->tc = NULL;
    1101            0 :   GNUNET_CONTAINER_DLL_remove (pdf_head,
    1102              :                                pdf_tail,
    1103              :                                po);
    1104            0 :   if (TALER_EC_NONE != tr->ec)
    1105              :   {
    1106              :     po->http_status
    1107            0 :       = TALER_ErrorCode_get_http_status (tr->ec);
    1108              :     po->response
    1109            0 :       = TALER_MHD_make_error (tr->ec,
    1110            0 :                               tr->details.hint);
    1111              :   }
    1112              :   else
    1113              :   {
    1114            0 :     po->http_status = MHD_HTTP_OK;
    1115            0 :     po->response = TALER_MHD_response_from_pdf_file (tr->details.filename);
    1116              :   }
    1117            0 :   MHD_resume_connection (po->con);
    1118            0 :   TALER_MHD_daemon_trigger ();
    1119            0 : }
    1120              : 
    1121              : 
    1122              : /**
    1123              :  * Build the final response for a completed (non-long-poll) request and
    1124              :  * queue it on @a connection.
    1125              :  *
    1126              :  * Handles all formats (JSON, CSV, XML, PDF).  For PDF this may suspend
    1127              :  * the connection while Typst runs asynchronously; in that case the caller
    1128              :  * must return #MHD_YES immediately.
    1129              :  *
    1130              :  * @param po the pending order state (already fully populated)
    1131              :  * @param connection the MHD connection
    1132              :  * @param mi the merchant instance
    1133              :  * @return MHD result code
    1134              :  */
    1135              : static enum MHD_Result
    1136           24 : reply_orders (struct TMH_PendingOrder *po,
    1137              :               struct MHD_Connection *connection,
    1138              :               struct TMH_MerchantInstance *mi)
    1139              : {
    1140              :   char total_buf[128];
    1141              :   char refund_buf[128];
    1142              :   char pending_buf[128];
    1143              : 
    1144           24 :   switch (po->format)
    1145              :   {
    1146           22 :   case POF_JSON:
    1147           22 :     return TALER_MHD_REPLY_JSON_PACK (
    1148              :       connection,
    1149              :       MHD_HTTP_OK,
    1150              :       GNUNET_JSON_pack_array_incref ("orders",
    1151              :                                      po->pa));
    1152            1 :   case POF_CSV:
    1153              :     {
    1154              :       struct MHD_Response *resp;
    1155              :       enum MHD_Result mret;
    1156              : 
    1157            2 :       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
    1158              :       {
    1159            1 :         struct TALER_Amount *tai = &po->total_amount.taa[i];
    1160              :         const struct TALER_Amount *r;
    1161              : 
    1162            1 :         strcpy (total_buf,
    1163              :                 TALER_amount2s (tai));
    1164            1 :         r = TALER_amount_set_find (tai->currency,
    1165            1 :                                    &po->total_refund_amount);
    1166            1 :         strcpy (refund_buf,
    1167              :                 TALER_amount2s (r));
    1168            1 :         r = TALER_amount_set_find (tai->currency,
    1169            1 :                                    &po->total_pending_refund_amount);
    1170            1 :         strcpy (pending_buf,
    1171              :                 TALER_amount2s (r));
    1172              : 
    1173            1 :         GNUNET_buffer_write_fstr (
    1174              :           &po->csv,
    1175              :           "Total (paid %s only),,,,%s,%s,%s,,,\r\n",
    1176            1 :           tai->currency,
    1177              :           total_buf,
    1178              :           refund_buf,
    1179              :           pending_buf);
    1180              :       }
    1181            1 :       GNUNET_buffer_write_str (&po->csv,
    1182              :                                CSV_FOOTER);
    1183            1 :       resp = MHD_create_response_from_buffer (po->csv.position,
    1184            1 :                                               po->csv.mem,
    1185              :                                               MHD_RESPMEM_MUST_COPY);
    1186            1 :       TALER_MHD_add_global_headers (resp,
    1187              :                                     false);
    1188            1 :       GNUNET_break (MHD_YES ==
    1189              :                     MHD_add_response_header (resp,
    1190              :                                              MHD_HTTP_HEADER_CONTENT_TYPE,
    1191              :                                              "text/csv"));
    1192            1 :       mret = MHD_queue_response (connection,
    1193              :                                  MHD_HTTP_OK,
    1194              :                                  resp);
    1195            1 :       MHD_destroy_response (resp);
    1196            1 :       return mret;
    1197              :     }
    1198            1 :   case POF_XML:
    1199              :     {
    1200              :       struct MHD_Response *resp;
    1201              :       enum MHD_Result mret;
    1202              : 
    1203            2 :       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
    1204              :       {
    1205            1 :         struct TALER_Amount *tai = &po->total_amount.taa[i];
    1206              :         const struct TALER_Amount *r;
    1207              : 
    1208            1 :         strcpy (total_buf,
    1209              :                 TALER_amount2s (tai));
    1210            1 :         r = TALER_amount_set_find (tai->currency,
    1211            1 :                                    &po->total_refund_amount);
    1212            1 :         strcpy (refund_buf,
    1213              :                 TALER_amount2s (r));
    1214            1 :         r = TALER_amount_set_find (tai->currency,
    1215            1 :                                    &po->total_pending_refund_amount);
    1216            1 :         strcpy (pending_buf,
    1217              :                 TALER_amount2s (r));
    1218              : 
    1219              :         /* Append totals row with paid and refunded amount columns */
    1220            1 :         GNUNET_buffer_write_fstr (
    1221              :           &po->xml,
    1222              :           "<Row>"
    1223              :           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">Total (paid %s only)</Data></Cell>"
    1224              :           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
    1225              :           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>"
    1226              :           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>"
    1227              :           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
    1228              :           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
    1229              :           "</Row>\n",
    1230            1 :           tai->currency,
    1231              :           total_buf,
    1232              :           refund_buf);
    1233              :       }
    1234            1 :       GNUNET_buffer_write_str (&po->xml,
    1235              :                                XML_FOOTER);
    1236            1 :       resp = MHD_create_response_from_buffer (po->xml.position,
    1237            1 :                                               po->xml.mem,
    1238              :                                               MHD_RESPMEM_MUST_COPY);
    1239            1 :       TALER_MHD_add_global_headers (resp,
    1240              :                                     false);
    1241            1 :       GNUNET_break (MHD_YES ==
    1242              :                     MHD_add_response_header (resp,
    1243              :                                              MHD_HTTP_HEADER_CONTENT_TYPE,
    1244              :                                              "application/vnd.ms-excel"));
    1245            1 :       mret = MHD_queue_response (connection,
    1246              :                                  MHD_HTTP_OK,
    1247              :                                  resp);
    1248            1 :       MHD_destroy_response (resp);
    1249            1 :       return mret;
    1250              :     }
    1251            0 :   case POF_PDF:
    1252              :     {
    1253              :       /* Build the JSON document for Typst, passing all totals */
    1254              :       json_t *root;
    1255              :       struct TALER_MHD_TypstDocument doc;
    1256            0 :       json_t *ta = json_array ();
    1257            0 :       json_t *ra = json_array ();
    1258            0 :       json_t *pa = json_array ();
    1259              : 
    1260            0 :       GNUNET_assert (NULL != ta);
    1261            0 :       GNUNET_assert (NULL != ra);
    1262            0 :       GNUNET_assert (NULL != pa);
    1263            0 :       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
    1264              :       {
    1265            0 :         struct TALER_Amount *tai = &po->total_amount.taa[i];
    1266              :         const struct TALER_Amount *r;
    1267              : 
    1268            0 :         GNUNET_assert (0 ==
    1269              :                        json_array_append_new (ta,
    1270              :                                               TALER_JSON_from_amount (tai)));
    1271            0 :         r = TALER_amount_set_find (tai->currency,
    1272            0 :                                    &po->total_refund_amount);
    1273            0 :         GNUNET_assert (0 ==
    1274              :                        json_array_append_new (ra,
    1275              :                                               TALER_JSON_from_amount (r)));
    1276            0 :         r = TALER_amount_set_find (tai->currency,
    1277            0 :                                    &po->total_pending_refund_amount);
    1278            0 :         GNUNET_assert (0 ==
    1279              :                        json_array_append_new (pa,
    1280              :                                               TALER_JSON_from_amount (r)));
    1281              :       }
    1282            0 :       root = GNUNET_JSON_PACK (
    1283              :         GNUNET_JSON_pack_string ("business_name",
    1284              :                                  mi->settings.name),
    1285              :         GNUNET_JSON_pack_array_incref ("orders",
    1286              :                                        po->pa),
    1287              :         GNUNET_JSON_pack_array_steal ("total_amounts",
    1288              :                                       ta),
    1289              :         GNUNET_JSON_pack_array_steal ("total_refund_amounts",
    1290              :                                       ra),
    1291              :         GNUNET_JSON_pack_array_steal ("total_pending_refund_amounts",
    1292              :                                       pa));
    1293            0 :       doc.form_name = "orders";
    1294            0 :       doc.form_version = "0.0.0";
    1295            0 :       doc.data = root;
    1296              : 
    1297            0 :       po->tc = TALER_MHD_typst (TALER_MERCHANT_project_data (),
    1298              :                                 TMH_cfg,
    1299              :                                 false,  /* remove on exit */
    1300              :                                 "merchant",
    1301              :                                 1,     /* one document */
    1302              :                                 &doc,
    1303              :                                 &pdf_cb,
    1304              :                                 po);
    1305            0 :       json_decref (root);
    1306            0 :       if (NULL == po->tc)
    1307              :       {
    1308            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1309              :                     "Client requested PDF, but Typst is unavailable\n");
    1310            0 :         return TALER_MHD_reply_with_error (
    1311              :           connection,
    1312              :           MHD_HTTP_NOT_IMPLEMENTED,
    1313              :           TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK,
    1314              :           NULL);
    1315              :       }
    1316            0 :       GNUNET_CONTAINER_DLL_insert (pdf_head,
    1317              :                                    pdf_tail,
    1318              :                                    po);
    1319            0 :       MHD_suspend_connection (connection);
    1320            0 :       return MHD_YES;
    1321              :     }
    1322              :   } /* end switch */
    1323            0 :   GNUNET_assert (0);
    1324              :   return MHD_NO;
    1325              : }
    1326              : 
    1327              : 
    1328              : /**
    1329              :  * Handle a GET "/orders" request.
    1330              :  *
    1331              :  * @param rh context of the handler
    1332              :  * @param connection the MHD connection to handle
    1333              :  * @param[in,out] hc context with further information about the request
    1334              :  * @return MHD result code
    1335              :  */
    1336              : enum MHD_Result
    1337           26 : TMH_private_get_orders (const struct TMH_RequestHandler *rh,
    1338              :                         struct MHD_Connection *connection,
    1339              :                         struct TMH_HandlerContext *hc)
    1340              : {
    1341           26 :   struct TMH_PendingOrder *po = hc->ctx;
    1342           26 :   struct TMH_MerchantInstance *mi = hc->instance;
    1343              :   enum GNUNET_DB_QueryStatus qs;
    1344              : 
    1345           26 :   if (NULL != po)
    1346              :   {
    1347            2 :     if (po->recheck)
    1348              :     {
    1349            0 :       if (NULL != po->order_timeout_task)
    1350              :       {
    1351            0 :         GNUNET_SCHEDULER_cancel (po->order_timeout_task);
    1352            0 :         po->order_timeout_task = NULL;
    1353              :       }
    1354            0 :       po->recheck = false;
    1355            0 :       goto run_query;
    1356              :     }
    1357            2 :     if (TALER_EC_NONE != po->result)
    1358              :     {
    1359              :       /* Resumed from long-polling with error */
    1360            0 :       GNUNET_break (0);
    1361            0 :       return TALER_MHD_reply_with_error (connection,
    1362              :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    1363              :                                          po->result,
    1364              :                                          NULL);
    1365              :     }
    1366            2 :     if (POF_PDF == po->format)
    1367              :     {
    1368              :       /* resumed from long-polling or from Typst PDF generation */
    1369              :       /* We really must have a response in this case */
    1370            0 :       if (NULL == po->response)
    1371              :       {
    1372            0 :         GNUNET_break (0);
    1373            0 :         return MHD_NO;
    1374              :       }
    1375            0 :       return MHD_queue_response (connection,
    1376              :                                  po->http_status,
    1377              :                                  po->response);
    1378              :     }
    1379            2 :     return reply_orders (po,
    1380              :                          connection,
    1381              :                          mi);
    1382              :   }
    1383           24 :   po = GNUNET_new (struct TMH_PendingOrder);
    1384           24 :   hc->ctx = po;
    1385           24 :   hc->cc = &cleanup;
    1386           24 :   po->con = connection;
    1387           24 :   po->pa = json_array ();
    1388           24 :   GNUNET_assert (NULL != po->pa);
    1389           24 :   po->instance_id = mi->settings.id;
    1390           24 :   po->mi = mi;
    1391              : 
    1392              :   /* Determine desired output format from Accept header */
    1393              :   {
    1394              :     const char *mime;
    1395              : 
    1396           24 :     mime = MHD_lookup_connection_value (connection,
    1397              :                                         MHD_HEADER_KIND,
    1398              :                                         MHD_HTTP_HEADER_ACCEPT);
    1399           24 :     if (NULL == mime)
    1400            0 :       mime = "application/json";
    1401           24 :     if (0 == strcmp (mime,
    1402              :                      "*/*"))
    1403           22 :       mime = "application/json";
    1404           24 :     if (0 == strcmp (mime,
    1405              :                      "application/json"))
    1406              :     {
    1407           22 :       po->format = POF_JSON;
    1408              :     }
    1409            2 :     else if (0 == strcmp (mime,
    1410              :                           "text/csv"))
    1411              :     {
    1412            1 :       po->format = POF_CSV;
    1413            1 :       GNUNET_buffer_write_str (&po->csv,
    1414              :                                CSV_HEADER);
    1415              :     }
    1416            1 :     else if (0 == strcmp (mime,
    1417              :                           "application/vnd.ms-excel"))
    1418              :     {
    1419            1 :       po->format = POF_XML;
    1420            1 :       GNUNET_buffer_write_str (&po->xml,
    1421              :                                XML_HEADER);
    1422              :     }
    1423            0 :     else if (0 == strcmp (mime,
    1424              :                           "application/pdf"))
    1425              :     {
    1426            0 :       po->format = POF_PDF;
    1427              :     }
    1428              :     else
    1429              :     {
    1430            0 :       GNUNET_break_op (0);
    1431            0 :       return TALER_MHD_REPLY_JSON_PACK (
    1432              :         connection,
    1433              :         MHD_HTTP_NOT_ACCEPTABLE,
    1434              :         GNUNET_JSON_pack_string ("hint",
    1435              :                                  mime));
    1436              :     }
    1437              :   }
    1438              : 
    1439           24 :   if (! (TALER_MHD_arg_to_yna (connection,
    1440              :                                "paid",
    1441              :                                TALER_EXCHANGE_YNA_ALL,
    1442              :                                &po->of.paid)) )
    1443              :   {
    1444            0 :     GNUNET_break_op (0);
    1445            0 :     return TALER_MHD_reply_with_error (connection,
    1446              :                                        MHD_HTTP_BAD_REQUEST,
    1447              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1448              :                                        "paid");
    1449              :   }
    1450           24 :   if (! (TALER_MHD_arg_to_yna (connection,
    1451              :                                "refunded",
    1452              :                                TALER_EXCHANGE_YNA_ALL,
    1453              :                                &po->of.refunded)) )
    1454              :   {
    1455            0 :     GNUNET_break_op (0);
    1456            0 :     return TALER_MHD_reply_with_error (connection,
    1457              :                                        MHD_HTTP_BAD_REQUEST,
    1458              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1459              :                                        "refunded");
    1460              :   }
    1461           24 :   if (! (TALER_MHD_arg_to_yna (connection,
    1462              :                                "wired",
    1463              :                                TALER_EXCHANGE_YNA_ALL,
    1464              :                                &po->of.wired)) )
    1465              :   {
    1466            0 :     GNUNET_break_op (0);
    1467            0 :     return TALER_MHD_reply_with_error (connection,
    1468              :                                        MHD_HTTP_BAD_REQUEST,
    1469              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1470              :                                        "wired");
    1471              :   }
    1472           24 :   if (! (TALER_MHD_arg_to_yna (connection,
    1473              :                                "expired",
    1474              :                                TALER_EXCHANGE_YNA_ALL,
    1475              :                                &po->of.expired)) )
    1476              :   {
    1477            0 :     GNUNET_break_op (0);
    1478            0 :     return TALER_MHD_reply_with_error (connection,
    1479              :                                        MHD_HTTP_BAD_REQUEST,
    1480              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1481              :                                        "expired");
    1482              :   }
    1483           24 :   po->of.delta = -20;
    1484              :   /* deprecated in protocol v12 */
    1485           24 :   TALER_MHD_parse_request_snumber (connection,
    1486              :                                    "delta",
    1487              :                                    &po->of.delta);
    1488              :   /* since protocol v12 */
    1489           24 :   TALER_MHD_parse_request_snumber (connection,
    1490              :                                    "limit",
    1491              :                                    &po->of.delta);
    1492           24 :   if ( (-MAX_DELTA > po->of.delta) ||
    1493           24 :        (po->of.delta > MAX_DELTA) )
    1494              :   {
    1495            0 :     GNUNET_break_op (0);
    1496            0 :     return TALER_MHD_reply_with_error (connection,
    1497              :                                        MHD_HTTP_BAD_REQUEST,
    1498              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1499              :                                        "limit");
    1500              :   }
    1501              :   {
    1502              :     const char *date_s_str;
    1503              : 
    1504           24 :     date_s_str = MHD_lookup_connection_value (connection,
    1505              :                                               MHD_GET_ARGUMENT_KIND,
    1506              :                                               "date_s");
    1507           24 :     if (NULL == date_s_str)
    1508              :     {
    1509           24 :       if (po->of.delta > 0)
    1510           10 :         po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
    1511              :       else
    1512           14 :         po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
    1513              :     }
    1514              :     else
    1515              :     {
    1516              :       char dummy;
    1517              :       unsigned long long ll;
    1518              : 
    1519            0 :       if (1 !=
    1520            0 :           sscanf (date_s_str,
    1521              :                   "%llu%c",
    1522              :                   &ll,
    1523              :                   &dummy))
    1524              :       {
    1525            0 :         GNUNET_break_op (0);
    1526            0 :         return TALER_MHD_reply_with_error (connection,
    1527              :                                            MHD_HTTP_BAD_REQUEST,
    1528              :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1529              :                                            "date_s");
    1530              :       }
    1531              : 
    1532            0 :       po->of.date = GNUNET_TIME_absolute_to_timestamp (
    1533              :         GNUNET_TIME_absolute_from_s (ll));
    1534            0 :       if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
    1535              :       {
    1536            0 :         GNUNET_break_op (0);
    1537            0 :         return TALER_MHD_reply_with_error (connection,
    1538              :                                            MHD_HTTP_BAD_REQUEST,
    1539              :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1540              :                                            "date_s");
    1541              :       }
    1542              :     }
    1543              :   }
    1544           24 :   if (po->of.delta > 0)
    1545              :   {
    1546              :     /* Only narrow the range if the client actually asked for it:
    1547              :        the default duration of zero would cut off everything
    1548              :        created before *now*. */
    1549           10 :     bool have_max_age
    1550              :       = (NULL !=
    1551           10 :          MHD_lookup_connection_value (connection,
    1552              :                                       MHD_GET_ARGUMENT_KIND,
    1553              :                                       "max_age"));
    1554           10 :     bool have_max_age_s
    1555              :       = (NULL !=
    1556           10 :          MHD_lookup_connection_value (connection,
    1557              :                                       MHD_GET_ARGUMENT_KIND,
    1558              :                                       "max_age_s"));
    1559           10 :     struct GNUNET_TIME_Relative duration = GNUNET_TIME_UNIT_ZERO;
    1560              : 
    1561           10 :     if (have_max_age)
    1562              :     {
    1563              :       /* deprecated in protocol v33, superseded by "max_age_s" */
    1564            4 :       TALER_MHD_parse_request_rel_time (connection,
    1565              :                                         "max_age",
    1566              :                                         &duration);
    1567              :     }
    1568           10 :     if (have_max_age_s)
    1569              :     {
    1570              :       /* since protocol v33 */
    1571            4 :       uint64_t max_age_s = 0;
    1572              :       struct GNUNET_TIME_Relative age;
    1573              : 
    1574            4 :       TALER_MHD_parse_request_number (connection,
    1575              :                                       "max_age_s",
    1576              :                                       &max_age_s);
    1577            4 :       age = GNUNET_TIME_relative_saturating_multiply (GNUNET_TIME_UNIT_SECONDS,
    1578              :                                                       max_age_s);
    1579            4 :       if (have_max_age &&
    1580            2 :           GNUNET_TIME_relative_cmp (duration,
    1581              :                                     !=,
    1582              :                                     age))
    1583              :       {
    1584            2 :         unsigned long long max_age_ms
    1585            2 :           = duration.rel_value_us
    1586            2 :             / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    1587              : 
    1588            2 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1589              :                     "Client gave conflicting values for `max_age' (%llu ms) and `max_age_s' (%llu s); using `max_age_s'\n",
    1590              :                     max_age_ms,
    1591              :                     (unsigned long long) max_age_s);
    1592              :       }
    1593            4 :       duration = age;
    1594              :     }
    1595           10 :     if (have_max_age || have_max_age_s)
    1596              :     {
    1597              :       struct GNUNET_TIME_Absolute cut_off;
    1598              : 
    1599            6 :       cut_off = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
    1600              :                                                duration);
    1601            6 :       po->of.date = GNUNET_TIME_timestamp_max (
    1602              :         po->of.date,
    1603              :         GNUNET_TIME_absolute_to_timestamp (cut_off));
    1604              :     }
    1605              :   }
    1606           24 :   if (po->of.delta > 0)
    1607           10 :     po->of.start_row = 0;
    1608              :   else
    1609           14 :     po->of.start_row = INT64_MAX;
    1610              :   /* deprecated in protocol v12 */
    1611           24 :   TALER_MHD_parse_request_number (connection,
    1612              :                                   "start",
    1613              :                                   &po->of.start_row);
    1614              :   /* since protocol v12 */
    1615           24 :   TALER_MHD_parse_request_number (connection,
    1616              :                                   "offset",
    1617              :                                   &po->of.start_row);
    1618           24 :   if (INT64_MAX < po->of.start_row)
    1619              :   {
    1620            0 :     GNUNET_break_op (0);
    1621            0 :     return TALER_MHD_reply_with_error (connection,
    1622              :                                        MHD_HTTP_BAD_REQUEST,
    1623              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1624              :                                        "offset");
    1625              :   }
    1626           24 :   po->summary_filter = tr (MHD_lookup_connection_value (connection,
    1627              :                                                         MHD_GET_ARGUMENT_KIND,
    1628              :                                                         "summary_filter"));
    1629           24 :   po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
    1630              :   po->of.session_id
    1631           24 :     = MHD_lookup_connection_value (connection,
    1632              :                                    MHD_GET_ARGUMENT_KIND,
    1633              :                                    "session_id");
    1634              :   po->of.fulfillment_url
    1635           24 :     = MHD_lookup_connection_value (connection,
    1636              :                                    MHD_GET_ARGUMENT_KIND,
    1637              :                                    "fulfillment_url");
    1638           24 :   TALER_MHD_parse_request_timeout (connection,
    1639              :                                    &po->long_poll_timeout);
    1640           24 :   if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
    1641              :   {
    1642            0 :     GNUNET_break_op (0);
    1643            0 :     return TALER_MHD_reply_with_error (connection,
    1644              :                                        MHD_HTTP_BAD_REQUEST,
    1645              :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1646              :                                        "timeout_ms");
    1647              :   }
    1648           24 :   if ( (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) &&
    1649            2 :        (NULL == mi->po_eh) )
    1650              :   {
    1651            2 :     struct TMH_OrderChangeEventP change_eh = {
    1652            2 :       .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
    1653            2 :       .header.size = htons (sizeof (change_eh)),
    1654              :       .merchant_pub = mi->merchant_pub
    1655              :     };
    1656              : 
    1657            2 :     mi->po_eh = TALER_MERCHANTDB_event_listen (TMH_db,
    1658              :                                                &change_eh.header,
    1659            2 :                                                GNUNET_TIME_UNIT_FOREVER_REL,
    1660              :                                                &resume_by_event,
    1661              :                                                mi);
    1662              :   }
    1663              : 
    1664           22 : run_query:
    1665           24 :   po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
    1666              : 
    1667           24 :   qs = TALER_MERCHANTDB_iterate_orders (TMH_db,
    1668              :                                         po->instance_id,
    1669           24 :                                         &po->of,
    1670              :                                         &add_order,
    1671              :                                         po);
    1672           24 :   if (0 > qs)
    1673              :   {
    1674            0 :     GNUNET_break (0);
    1675            0 :     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    1676              :   }
    1677           24 :   if (TALER_EC_NONE != po->result)
    1678              :   {
    1679            0 :     GNUNET_break (0);
    1680            0 :     return TALER_MHD_reply_with_error (connection,
    1681              :                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
    1682              :                                        po->result,
    1683              :                                        NULL);
    1684              :   }
    1685           33 :   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
    1686            9 :        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
    1687              :   {
    1688            2 :     struct GNUNET_TIME_Absolute wakeup = po->long_poll_timeout;
    1689              : 
    1690            2 :     if (TALER_EXCHANGE_YNA_YES == po->of.expired)
    1691            0 :       wakeup = GNUNET_TIME_absolute_min (
    1692              :         wakeup,
    1693              :         GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_SECONDS));
    1694            2 :     GNUNET_assert (NULL == po->order_timeout_task);
    1695              :     po->order_timeout_task
    1696            2 :       = GNUNET_SCHEDULER_add_at (wakeup,
    1697              :                                  &order_timeout,
    1698              :                                  po);
    1699            2 :     GNUNET_CONTAINER_DLL_insert (mi->po_head,
    1700              :                                  mi->po_tail,
    1701              :                                  po);
    1702            2 :     po->in_dll = true;
    1703            2 :     MHD_suspend_connection (connection);
    1704            2 :     return MHD_YES;
    1705              :   }
    1706           22 :   return reply_orders (po,
    1707              :                        connection,
    1708              :                        mi);
    1709              : }
    1710              : 
    1711              : 
    1712              : /* end of taler-merchant-httpd_get-private-orders.c */
        

Generated by: LCOV version 2.0-1