merchant

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

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


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