merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_get-private-orders.c (50249B)


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