merchant

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

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


      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 
    615         if (-1 == choice_index)
    616           choice_index = 0; /* default choice */
    617         GNUNET_assert (choice_index < contract->pc->details.v1.choices_len);
    618         choice = &contract->pc->details.v1.choices[choice_index];
    619         brutto = &choice->amount;
    620       }
    621       break;
    622     default:
    623       GNUNET_break (0);
    624       goto cleanup;
    625     }
    626     GNUNET_assert (GNUNET_OK ==
    627                    TALER_amount_set_zero (brutto->currency,
    628                                           &prc.total_refund_amount));
    629     GNUNET_assert (GNUNET_OK ==
    630                    TALER_amount_set_zero (brutto->currency,
    631                                           &prc.pending_refund_amount));
    632 
    633     qs = TALER_MERCHANTDB_lookup_refunds_detailed (TMH_db,
    634                                                    po->instance_id,
    635                                                    &h_contract_terms,
    636                                                    &process_refunds_cb,
    637                                                    &prc);
    638     if (0 > qs)
    639     {
    640       GNUNET_break (0);
    641       po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
    642       goto cleanup;
    643     }
    644     if (TALER_EC_NONE != prc.ec)
    645     {
    646       GNUNET_break (0);
    647       po->result = prc.ec;
    648       goto cleanup;
    649     }
    650     if (0 > TALER_amount_cmp (&prc.total_refund_amount,
    651                               brutto) &&
    652         GNUNET_TIME_absolute_is_future (
    653           contract->pc->refund_deadline.abs_time))
    654       refundable = true;
    655   }
    656 
    657   /* compute amount totals */
    658   amount = NULL;
    659   switch (ct->version)
    660   {
    661   case TALER_MERCHANT_CONTRACT_VERSION_0:
    662     {
    663       amount = (NULL != contract)
    664         ? &contract->pc->details.v0.brutto
    665         : &order->details.v0.brutto;
    666 
    667       if (TALER_amount_is_zero (amount) &&
    668           (po->of.wired != TALER_EXCHANGE_YNA_ALL) )
    669       {
    670         /* If we are actually filtering by wire status,
    671            and the order was over an amount of zero,
    672            do not return it as wire status is not
    673            exactly meaningful for orders over zero. */
    674         goto cleanup;
    675       }
    676 
    677       /* Accumulate order total */
    678       if (paid)
    679         accumulate_total (po,
    680                           amount);
    681       if (TALER_EC_NONE != po->result)
    682         goto cleanup;
    683       /* Accumulate refund totals (only meaningful for paid orders) */
    684       if (paid)
    685       {
    686         accumulate_refund_totals (po,
    687                                   &prc.total_refund_amount,
    688                                   &prc.pending_refund_amount);
    689         if (TALER_EC_NONE != po->result)
    690           goto cleanup;
    691       }
    692     }
    693     break;
    694   case TALER_MERCHANT_CONTRACT_VERSION_1:
    695     if (-1 == choice_index)
    696       choice_index = 0; /* default choice */
    697     if (NULL != contract)
    698     {
    699       struct TALER_MERCHANT_ContractChoice *choice
    700         = &contract->pc->details.v1.choices[choice_index];
    701 
    702       GNUNET_assert (choice_index < contract->pc->details.v1.choices_len);
    703       amount = &choice->amount;
    704       /* Accumulate order total */
    705       if (paid)
    706         accumulate_total (po,
    707                           amount);
    708       if (TALER_EC_NONE != po->result)
    709         goto cleanup;
    710       /* Accumulate refund totals (only meaningful for paid orders) */
    711       if (paid)
    712       {
    713         accumulate_refund_totals (po,
    714                                   &prc.total_refund_amount,
    715                                   &prc.pending_refund_amount);
    716         if (TALER_EC_NONE != po->result)
    717           goto cleanup;
    718       }
    719     }
    720     else
    721     {
    722       GNUNET_assert (choice_index < order->details.v1.choices_len);
    723       amount = &order->details.v1.choices[choice_index].amount;
    724       if (TALER_EC_NONE != po->result)
    725         goto cleanup;
    726     }
    727     break;
    728   default:
    729     GNUNET_break (0);
    730     po->result = TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION;
    731     goto cleanup;
    732   }
    733 
    734   /* convert amounts to strings (needed for some formats) */
    735   /* FIXME: use currency formatting rules in the future
    736      instead of TALER_amount2s for human readability... */
    737   strcpy (amount_buf,
    738           TALER_amount2s (amount));
    739   if (paid)
    740     strcpy (refund_buf,
    741             TALER_amount2s (&prc.total_refund_amount));
    742   if (paid)
    743     strcpy (pending_buf,
    744             TALER_amount2s (&prc.pending_refund_amount));
    745 
    746   switch (po->format)
    747   {
    748   case POF_JSON:
    749   case POF_PDF:
    750     GNUNET_assert (
    751       0 ==
    752       json_array_append_new (
    753         po->pa,
    754         GNUNET_JSON_PACK (
    755           GNUNET_JSON_pack_string ("order_id",
    756                                    order_id),
    757           GNUNET_JSON_pack_uint64 ("row_id",
    758                                    order_serial),
    759           GNUNET_JSON_pack_timestamp ("timestamp",
    760                                       creation_time),
    761           TALER_JSON_pack_amount ("amount",
    762                                   amount),
    763           GNUNET_JSON_pack_allow_null (
    764             TALER_JSON_pack_amount (
    765               "refund_amount",
    766               paid
    767                 ? &prc.total_refund_amount
    768                 : NULL)),
    769           GNUNET_JSON_pack_allow_null (
    770             TALER_JSON_pack_amount (
    771               "pending_refund_amount",
    772               paid
    773                 ? &prc.pending_refund_amount
    774                 : NULL)),
    775           GNUNET_JSON_pack_string ("summary",
    776                                    ct->summary),
    777           GNUNET_JSON_pack_bool ("refundable",
    778                                  refundable),
    779           GNUNET_JSON_pack_bool ("paid",
    780                                  paid))));
    781     break;
    782   case POF_CSV:
    783     {
    784       size_t len = strlen (ct->summary);
    785       size_t wpos = 0;
    786       char *esummary;
    787       struct tm *tm;
    788       time_t t;
    789 
    790       /* Escape 'summary' to double '"' as per RFC 4180, 2.7. */
    791       esummary = GNUNET_malloc (2 * len + 1);
    792       for (size_t off = 0; off<len; off++)
    793       {
    794         if ('"' == ct->summary[off])
    795           esummary[wpos++] = '"';
    796         esummary[wpos++] = ct->summary[off];
    797       }
    798       t = GNUNET_TIME_timestamp_to_s (creation_time);
    799       tm = localtime (&t);
    800       GNUNET_buffer_write_fstr (
    801         &po->csv,
    802         "%s,%llu,%04u-%02u-%02u,%02u:%02u (%s),%llu,%s,%s,%s,\"%s\",%s,%s\r\n",
    803         order_id,
    804         (unsigned long long) order_serial,
    805         tm->tm_year + 1900,
    806         tm->tm_mon + 1,
    807         tm->tm_mday,
    808         tm->tm_hour,
    809         tm->tm_min,
    810         tm->tm_zone,
    811         (unsigned long long) t,
    812         amount_buf,
    813         paid ? refund_buf : "",
    814         paid ? pending_buf : "",
    815         esummary,
    816         refundable ? "yes" : "no",
    817         paid ? "yes" : "no");
    818       GNUNET_free (esummary);
    819       break;
    820     }
    821   case POF_XML:
    822     {
    823       char *esummary = TALER_escape_xml (ct->summary);
    824       char creation_time_s[128];
    825       const struct tm *tm;
    826       time_t tt;
    827 
    828       tt = (time_t) GNUNET_TIME_timestamp_to_s (creation_time);
    829       tm = gmtime (&tt);
    830       strftime (creation_time_s,
    831                 sizeof (creation_time_s),
    832                 "%Y-%m-%dT%H:%M:%S",
    833                 tm);
    834       GNUNET_buffer_write_fstr (
    835         &po->xml,
    836         "<Row>"
    837         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    838         "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"DateTime\">%s</Data></Cell>"
    839         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    840         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    841         "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
    842         "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>"
    843         "</Row>\n",
    844         order_id,
    845         creation_time_s,
    846         amount_buf,
    847         paid ? refund_buf : "",
    848         NULL != esummary ? esummary : "",
    849         paid ? "TRUE" : "FALSE",
    850         paid ? "1" : "0");
    851       GNUNET_free (esummary);
    852     }
    853     break;
    854   }    /* end switch po->format */
    855 
    856 cleanup:
    857   GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    858                 TALER_MERCHANTDB_set_instance (
    859                   TMH_db,
    860                   NULL));
    861   json_decref (terms);
    862   GNUNET_free (order_id);
    863   if (NULL != order)
    864   {
    865     TALER_MERCHANT_order_free (order);
    866     order = NULL;
    867   }
    868   if (NULL != contract)
    869   {
    870     TALER_MERCHANT_contract_free (contract);
    871     contract = NULL;
    872   }
    873 }
    874 
    875 
    876 /**
    877  * We have received a trigger from the database
    878  * that we should (possibly) resume some requests.
    879  *
    880  * @param cls a `struct TMH_MerchantInstance`
    881  * @param extra a `struct TMH_OrderChangeEventP`
    882  * @param extra_size number of bytes in @a extra
    883  */
    884 static void
    885 resume_by_event (void *cls,
    886                  const void *extra,
    887                  size_t extra_size)
    888 {
    889   struct TMH_MerchantInstance *mi = cls;
    890   const struct TMH_OrderChangeEventDetailsP *oce = extra;
    891   struct TMH_PendingOrder *pn;
    892   enum TMH_OrderStateFlags osf;
    893   uint64_t order_serial_id;
    894   struct GNUNET_TIME_Timestamp date;
    895 
    896   if (sizeof (*oce) != extra_size)
    897   {
    898     GNUNET_break (0);
    899     return;
    900   }
    901   osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state);
    902   order_serial_id = GNUNET_ntohll (oce->order_serial_id);
    903   date = GNUNET_TIME_timestamp_ntoh (oce->execution_date);
    904   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    905               "Received notification about order %llu\n",
    906               (unsigned long long) order_serial_id);
    907   for (struct TMH_PendingOrder *po = mi->po_head;
    908        NULL != po;
    909        po = pn)
    910   {
    911     pn = po->next;
    912     if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) ==
    913                (0 != (osf & TMH_OSF_PAID))) ||
    914               (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) &&
    915             ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) ==
    916                (0 != (osf & TMH_OSF_REFUNDED))) ||
    917               (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) &&
    918             ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) ==
    919                (0 != (osf & TMH_OSF_WIRED))) ||
    920               (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) )
    921     {
    922       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    923                   "Client %p waits on different order type\n",
    924                   po);
    925       continue;
    926     }
    927     if (po->of.delta > 0)
    928     {
    929       if (order_serial_id < po->of.start_row)
    930       {
    931         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    932                     "Client %p waits on different order row\n",
    933                     po);
    934         continue;
    935       }
    936       if (GNUNET_TIME_timestamp_cmp (date,
    937                                      <,
    938                                      po->of.date))
    939       {
    940         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    941                     "Client %p waits on different order date\n",
    942                     po);
    943         continue;
    944       }
    945       po->of.delta--;
    946     }
    947     else
    948     {
    949       if (order_serial_id > po->of.start_row)
    950       {
    951         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    952                     "Client %p waits on different order row\n",
    953                     po);
    954         continue;
    955       }
    956       if (GNUNET_TIME_timestamp_cmp (date,
    957                                      >,
    958                                      po->of.date))
    959       {
    960         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    961                     "Client %p waits on different order date\n",
    962                     po);
    963         continue;
    964       }
    965       po->of.delta++;
    966     }
    967     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    968                 "Waking up client %p!\n",
    969                 po);
    970     add_order (po,
    971                NULL,
    972                order_serial_id,
    973                date);
    974     GNUNET_assert (po->in_dll);
    975     GNUNET_CONTAINER_DLL_remove (mi->po_head,
    976                                  mi->po_tail,
    977                                  po);
    978     po->in_dll = false;
    979     MHD_resume_connection (po->con);
    980     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    981   }
    982   if (NULL == mi->po_head)
    983   {
    984     TALER_MERCHANTDB_event_listen_cancel (mi->po_eh);
    985     mi->po_eh = NULL;
    986   }
    987 }
    988 
    989 
    990 /**
    991  * There has been a change or addition of a new @a order_id.  Wake up
    992  * long-polling clients that may have been waiting for this event.
    993  *
    994  * @param mi the instance where the order changed
    995  * @param osf order state flags
    996  * @param date execution date of the order
    997  * @param order_serial_id serial ID of the order in the database
    998  */
    999 void
   1000 TMH_notify_order_change (struct TMH_MerchantInstance *mi,
   1001                          enum TMH_OrderStateFlags osf,
   1002                          struct GNUNET_TIME_Timestamp date,
   1003                          uint64_t order_serial_id)
   1004 {
   1005   struct TMH_OrderChangeEventDetailsP oce = {
   1006     .order_serial_id = GNUNET_htonll (order_serial_id),
   1007     .execution_date = GNUNET_TIME_timestamp_hton (date),
   1008     .order_state = htonl (osf)
   1009   };
   1010   struct TMH_OrderChangeEventP eh = {
   1011     .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
   1012     .header.size = htons (sizeof (eh)),
   1013     .merchant_pub = mi->merchant_pub
   1014   };
   1015 
   1016   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1017               "Notifying clients of new order %llu at %s\n",
   1018               (unsigned long long) order_serial_id,
   1019               TALER_B2S (&mi->merchant_pub));
   1020   TALER_MERCHANTDB_event_notify (TMH_db,
   1021                                  &eh.header,
   1022                                  &oce,
   1023                                  sizeof (oce));
   1024 }
   1025 
   1026 
   1027 /**
   1028  * Transforms an (untrusted) input filter into a Postgresql LIKE filter.
   1029  * Escapes "%" and "_" in the @a input and adds "%" at the beginning
   1030  * and the end to turn the @a input into a suitable Postgresql argument.
   1031  *
   1032  * @param input text to turn into a substring match expression, or NULL
   1033  * @return NULL if @a input was NULL, otherwise transformed @a input
   1034  */
   1035 static char *
   1036 tr (const char *input)
   1037 {
   1038   char *out;
   1039   size_t slen;
   1040   size_t wpos;
   1041 
   1042   if (NULL == input)
   1043     return NULL;
   1044   slen = strlen (input);
   1045   out = GNUNET_malloc (slen * 2 + 3);
   1046   wpos = 0;
   1047   out[wpos++] = '%';
   1048   for (size_t i = 0; i<slen; i++)
   1049   {
   1050     char c = input[i];
   1051 
   1052     if ( (c == '%') ||
   1053          (c == '_') )
   1054       out[wpos++] = '\\';
   1055     out[wpos++] = c;
   1056   }
   1057   out[wpos++] = '%';
   1058   GNUNET_assert (wpos < slen * 2 + 3);
   1059   return out;
   1060 }
   1061 
   1062 
   1063 /**
   1064  * Function called with the result of a #TALER_MHD_typst() operation.
   1065  *
   1066  * @param cls closure, a `struct TMH_PendingOrder *`
   1067  * @param tr result of the operation
   1068  */
   1069 static void
   1070 pdf_cb (void *cls,
   1071         const struct TALER_MHD_TypstResponse *tr)
   1072 {
   1073   struct TMH_PendingOrder *po = cls;
   1074 
   1075   po->tc = NULL;
   1076   GNUNET_CONTAINER_DLL_remove (pdf_head,
   1077                                pdf_tail,
   1078                                po);
   1079   if (TALER_EC_NONE != tr->ec)
   1080   {
   1081     po->http_status
   1082       = TALER_ErrorCode_get_http_status (tr->ec);
   1083     po->response
   1084       = TALER_MHD_make_error (tr->ec,
   1085                               tr->details.hint);
   1086   }
   1087   else
   1088   {
   1089     po->http_status = MHD_HTTP_OK;
   1090     po->response = TALER_MHD_response_from_pdf_file (tr->details.filename);
   1091   }
   1092   MHD_resume_connection (po->con);
   1093   TALER_MHD_daemon_trigger ();
   1094 }
   1095 
   1096 
   1097 /**
   1098  * Build the final response for a completed (non-long-poll) request and
   1099  * queue it on @a connection.
   1100  *
   1101  * Handles all formats (JSON, CSV, XML, PDF).  For PDF this may suspend
   1102  * the connection while Typst runs asynchronously; in that case the caller
   1103  * must return #MHD_YES immediately.
   1104  *
   1105  * @param po the pending order state (already fully populated)
   1106  * @param connection the MHD connection
   1107  * @param mi the merchant instance
   1108  * @return MHD result code
   1109  */
   1110 static enum MHD_Result
   1111 reply_orders (struct TMH_PendingOrder *po,
   1112               struct MHD_Connection *connection,
   1113               struct TMH_MerchantInstance *mi)
   1114 {
   1115   char total_buf[128];
   1116   char refund_buf[128];
   1117   char pending_buf[128];
   1118 
   1119   switch (po->format)
   1120   {
   1121   case POF_JSON:
   1122     return TALER_MHD_REPLY_JSON_PACK (
   1123       connection,
   1124       MHD_HTTP_OK,
   1125       GNUNET_JSON_pack_array_incref ("orders",
   1126                                      po->pa));
   1127   case POF_CSV:
   1128     {
   1129       struct MHD_Response *resp;
   1130       enum MHD_Result mret;
   1131 
   1132       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
   1133       {
   1134         struct TALER_Amount *tai = &po->total_amount.taa[i];
   1135         const struct TALER_Amount *r;
   1136 
   1137         strcpy (total_buf,
   1138                 TALER_amount2s (tai));
   1139         r = TALER_amount_set_find (tai->currency,
   1140                                    &po->total_refund_amount);
   1141         strcpy (refund_buf,
   1142                 TALER_amount2s (r));
   1143         r = TALER_amount_set_find (tai->currency,
   1144                                    &po->total_pending_refund_amount);
   1145         strcpy (pending_buf,
   1146                 TALER_amount2s (r));
   1147 
   1148         GNUNET_buffer_write_fstr (
   1149           &po->csv,
   1150           "Total (paid %s only),,,,%s,%s,%s,,,\r\n",
   1151           tai->currency,
   1152           total_buf,
   1153           refund_buf,
   1154           pending_buf);
   1155       }
   1156       GNUNET_buffer_write_str (&po->csv,
   1157                                CSV_FOOTER);
   1158       resp = MHD_create_response_from_buffer (po->csv.position,
   1159                                               po->csv.mem,
   1160                                               MHD_RESPMEM_MUST_COPY);
   1161       TALER_MHD_add_global_headers (resp,
   1162                                     false);
   1163       GNUNET_break (MHD_YES ==
   1164                     MHD_add_response_header (resp,
   1165                                              MHD_HTTP_HEADER_CONTENT_TYPE,
   1166                                              "text/csv"));
   1167       mret = MHD_queue_response (connection,
   1168                                  MHD_HTTP_OK,
   1169                                  resp);
   1170       MHD_destroy_response (resp);
   1171       return mret;
   1172     }
   1173   case POF_XML:
   1174     {
   1175       struct MHD_Response *resp;
   1176       enum MHD_Result mret;
   1177 
   1178       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
   1179       {
   1180         struct TALER_Amount *tai = &po->total_amount.taa[i];
   1181         const struct TALER_Amount *r;
   1182 
   1183         strcpy (total_buf,
   1184                 TALER_amount2s (tai));
   1185         r = TALER_amount_set_find (tai->currency,
   1186                                    &po->total_refund_amount);
   1187         strcpy (refund_buf,
   1188                 TALER_amount2s (r));
   1189         r = TALER_amount_set_find (tai->currency,
   1190                                    &po->total_pending_refund_amount);
   1191         strcpy (pending_buf,
   1192                 TALER_amount2s (r));
   1193 
   1194         /* Append totals row with paid and refunded amount columns */
   1195         GNUNET_buffer_write_fstr (
   1196           &po->xml,
   1197           "<Row>"
   1198           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">Total (paid %s only)</Data></Cell>"
   1199           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
   1200           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>"
   1201           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>"
   1202           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
   1203           "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>"
   1204           "</Row>\n",
   1205           tai->currency,
   1206           total_buf,
   1207           refund_buf);
   1208       }
   1209       GNUNET_buffer_write_str (&po->xml,
   1210                                XML_FOOTER);
   1211       resp = MHD_create_response_from_buffer (po->xml.position,
   1212                                               po->xml.mem,
   1213                                               MHD_RESPMEM_MUST_COPY);
   1214       TALER_MHD_add_global_headers (resp,
   1215                                     false);
   1216       GNUNET_break (MHD_YES ==
   1217                     MHD_add_response_header (resp,
   1218                                              MHD_HTTP_HEADER_CONTENT_TYPE,
   1219                                              "application/vnd.ms-excel"));
   1220       mret = MHD_queue_response (connection,
   1221                                  MHD_HTTP_OK,
   1222                                  resp);
   1223       MHD_destroy_response (resp);
   1224       return mret;
   1225     }
   1226   case POF_PDF:
   1227     {
   1228       /* Build the JSON document for Typst, passing all totals */
   1229       json_t *root;
   1230       struct TALER_MHD_TypstDocument doc;
   1231       json_t *ta = json_array ();
   1232       json_t *ra = json_array ();
   1233       json_t *pa = json_array ();
   1234 
   1235       GNUNET_assert (NULL != ta);
   1236       GNUNET_assert (NULL != ra);
   1237       GNUNET_assert (NULL != pa);
   1238       for (unsigned int i = 0; i<po->total_amount.taa_size; i++)
   1239       {
   1240         struct TALER_Amount *tai = &po->total_amount.taa[i];
   1241         const struct TALER_Amount *r;
   1242 
   1243         GNUNET_assert (0 ==
   1244                        json_array_append_new (ta,
   1245                                               TALER_JSON_from_amount (tai)));
   1246         r = TALER_amount_set_find (tai->currency,
   1247                                    &po->total_refund_amount);
   1248         GNUNET_assert (0 ==
   1249                        json_array_append_new (ra,
   1250                                               TALER_JSON_from_amount (r)));
   1251         r = TALER_amount_set_find (tai->currency,
   1252                                    &po->total_pending_refund_amount);
   1253         GNUNET_assert (0 ==
   1254                        json_array_append_new (pa,
   1255                                               TALER_JSON_from_amount (r)));
   1256       }
   1257       root = GNUNET_JSON_PACK (
   1258         GNUNET_JSON_pack_string ("business_name",
   1259                                  mi->settings.name),
   1260         GNUNET_JSON_pack_array_incref ("orders",
   1261                                        po->pa),
   1262         GNUNET_JSON_pack_array_steal ("total_amounts",
   1263                                       ta),
   1264         GNUNET_JSON_pack_array_steal ("total_refund_amounts",
   1265                                       ra),
   1266         GNUNET_JSON_pack_array_steal ("total_pending_refund_amounts",
   1267                                       pa));
   1268       doc.form_name = "orders";
   1269       doc.form_version = "0.0.0";
   1270       doc.data = root;
   1271 
   1272       po->tc = TALER_MHD_typst (TALER_MERCHANT_project_data (),
   1273                                 TMH_cfg,
   1274                                 false,  /* remove on exit */
   1275                                 "merchant",
   1276                                 1,     /* one document */
   1277                                 &doc,
   1278                                 &pdf_cb,
   1279                                 po);
   1280       json_decref (root);
   1281       if (NULL == po->tc)
   1282       {
   1283         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1284                     "Client requested PDF, but Typst is unavailable\n");
   1285         return TALER_MHD_reply_with_error (
   1286           connection,
   1287           MHD_HTTP_NOT_IMPLEMENTED,
   1288           TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK,
   1289           NULL);
   1290       }
   1291       GNUNET_CONTAINER_DLL_insert (pdf_head,
   1292                                    pdf_tail,
   1293                                    po);
   1294       MHD_suspend_connection (connection);
   1295       return MHD_YES;
   1296     }
   1297   } /* end switch */
   1298   GNUNET_assert (0);
   1299   return MHD_NO;
   1300 }
   1301 
   1302 
   1303 /**
   1304  * Handle a GET "/orders" request.
   1305  *
   1306  * @param rh context of the handler
   1307  * @param connection the MHD connection to handle
   1308  * @param[in,out] hc context with further information about the request
   1309  * @return MHD result code
   1310  */
   1311 enum MHD_Result
   1312 TMH_private_get_orders (const struct TMH_RequestHandler *rh,
   1313                         struct MHD_Connection *connection,
   1314                         struct TMH_HandlerContext *hc)
   1315 {
   1316   struct TMH_PendingOrder *po = hc->ctx;
   1317   struct TMH_MerchantInstance *mi = hc->instance;
   1318   enum GNUNET_DB_QueryStatus qs;
   1319 
   1320   if (NULL != po)
   1321   {
   1322     if (TALER_EC_NONE != po->result)
   1323     {
   1324       /* Resumed from long-polling with error */
   1325       GNUNET_break (0);
   1326       return TALER_MHD_reply_with_error (connection,
   1327                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
   1328                                          po->result,
   1329                                          NULL);
   1330     }
   1331     if (POF_PDF == po->format)
   1332     {
   1333       /* resumed from long-polling or from Typst PDF generation */
   1334       /* We really must have a response in this case */
   1335       if (NULL == po->response)
   1336       {
   1337         GNUNET_break (0);
   1338         return MHD_NO;
   1339       }
   1340       return MHD_queue_response (connection,
   1341                                  po->http_status,
   1342                                  po->response);
   1343     }
   1344     return reply_orders (po,
   1345                          connection,
   1346                          mi);
   1347   }
   1348   po = GNUNET_new (struct TMH_PendingOrder);
   1349   hc->ctx = po;
   1350   hc->cc = &cleanup;
   1351   po->con = connection;
   1352   po->pa = json_array ();
   1353   GNUNET_assert (NULL != po->pa);
   1354   po->instance_id = mi->settings.id;
   1355   po->mi = mi;
   1356 
   1357   /* Determine desired output format from Accept header */
   1358   {
   1359     const char *mime;
   1360 
   1361     mime = MHD_lookup_connection_value (connection,
   1362                                         MHD_HEADER_KIND,
   1363                                         MHD_HTTP_HEADER_ACCEPT);
   1364     if (NULL == mime)
   1365       mime = "application/json";
   1366     if (0 == strcmp (mime,
   1367                      "*/*"))
   1368       mime = "application/json";
   1369     if (0 == strcmp (mime,
   1370                      "application/json"))
   1371     {
   1372       po->format = POF_JSON;
   1373     }
   1374     else if (0 == strcmp (mime,
   1375                           "text/csv"))
   1376     {
   1377       po->format = POF_CSV;
   1378       GNUNET_buffer_write_str (&po->csv,
   1379                                CSV_HEADER);
   1380     }
   1381     else if (0 == strcmp (mime,
   1382                           "application/vnd.ms-excel"))
   1383     {
   1384       po->format = POF_XML;
   1385       GNUNET_buffer_write_str (&po->xml,
   1386                                XML_HEADER);
   1387     }
   1388     else if (0 == strcmp (mime,
   1389                           "application/pdf"))
   1390     {
   1391       po->format = POF_PDF;
   1392     }
   1393     else
   1394     {
   1395       GNUNET_break_op (0);
   1396       return TALER_MHD_REPLY_JSON_PACK (
   1397         connection,
   1398         MHD_HTTP_NOT_ACCEPTABLE,
   1399         GNUNET_JSON_pack_string ("hint",
   1400                                  mime));
   1401     }
   1402   }
   1403 
   1404   if (! (TALER_MHD_arg_to_yna (connection,
   1405                                "paid",
   1406                                TALER_EXCHANGE_YNA_ALL,
   1407                                &po->of.paid)) )
   1408   {
   1409     GNUNET_break_op (0);
   1410     return TALER_MHD_reply_with_error (connection,
   1411                                        MHD_HTTP_BAD_REQUEST,
   1412                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1413                                        "paid");
   1414   }
   1415   if (! (TALER_MHD_arg_to_yna (connection,
   1416                                "refunded",
   1417                                TALER_EXCHANGE_YNA_ALL,
   1418                                &po->of.refunded)) )
   1419   {
   1420     GNUNET_break_op (0);
   1421     return TALER_MHD_reply_with_error (connection,
   1422                                        MHD_HTTP_BAD_REQUEST,
   1423                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1424                                        "refunded");
   1425   }
   1426   if (! (TALER_MHD_arg_to_yna (connection,
   1427                                "wired",
   1428                                TALER_EXCHANGE_YNA_ALL,
   1429                                &po->of.wired)) )
   1430   {
   1431     GNUNET_break_op (0);
   1432     return TALER_MHD_reply_with_error (connection,
   1433                                        MHD_HTTP_BAD_REQUEST,
   1434                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1435                                        "wired");
   1436   }
   1437   po->of.delta = -20;
   1438   /* deprecated in protocol v12 */
   1439   TALER_MHD_parse_request_snumber (connection,
   1440                                    "delta",
   1441                                    &po->of.delta);
   1442   /* since protocol v12 */
   1443   TALER_MHD_parse_request_snumber (connection,
   1444                                    "limit",
   1445                                    &po->of.delta);
   1446   if ( (-MAX_DELTA > po->of.delta) ||
   1447        (po->of.delta > MAX_DELTA) )
   1448   {
   1449     GNUNET_break_op (0);
   1450     return TALER_MHD_reply_with_error (connection,
   1451                                        MHD_HTTP_BAD_REQUEST,
   1452                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1453                                        "limit");
   1454   }
   1455   {
   1456     const char *date_s_str;
   1457 
   1458     date_s_str = MHD_lookup_connection_value (connection,
   1459                                               MHD_GET_ARGUMENT_KIND,
   1460                                               "date_s");
   1461     if (NULL == date_s_str)
   1462     {
   1463       if (po->of.delta > 0)
   1464         po->of.date = GNUNET_TIME_UNIT_ZERO_TS;
   1465       else
   1466         po->of.date = GNUNET_TIME_UNIT_FOREVER_TS;
   1467     }
   1468     else
   1469     {
   1470       char dummy;
   1471       unsigned long long ll;
   1472 
   1473       if (1 !=
   1474           sscanf (date_s_str,
   1475                   "%llu%c",
   1476                   &ll,
   1477                   &dummy))
   1478       {
   1479         GNUNET_break_op (0);
   1480         return TALER_MHD_reply_with_error (connection,
   1481                                            MHD_HTTP_BAD_REQUEST,
   1482                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1483                                            "date_s");
   1484       }
   1485 
   1486       po->of.date = GNUNET_TIME_absolute_to_timestamp (
   1487         GNUNET_TIME_absolute_from_s (ll));
   1488       if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time))
   1489       {
   1490         GNUNET_break_op (0);
   1491         return TALER_MHD_reply_with_error (connection,
   1492                                            MHD_HTTP_BAD_REQUEST,
   1493                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1494                                            "date_s");
   1495       }
   1496     }
   1497   }
   1498   if (po->of.delta > 0)
   1499   {
   1500     struct GNUNET_TIME_Relative duration
   1501       = GNUNET_TIME_UNIT_FOREVER_REL;
   1502     struct GNUNET_TIME_Absolute cut_off;
   1503 
   1504     TALER_MHD_parse_request_rel_time (connection,
   1505                                       "max_age",
   1506                                       &duration);
   1507     cut_off = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
   1508                                              duration);
   1509     po->of.date = GNUNET_TIME_timestamp_max (
   1510       po->of.date,
   1511       GNUNET_TIME_absolute_to_timestamp (cut_off));
   1512   }
   1513   if (po->of.delta > 0)
   1514     po->of.start_row = 0;
   1515   else
   1516     po->of.start_row = INT64_MAX;
   1517   /* deprecated in protocol v12 */
   1518   TALER_MHD_parse_request_number (connection,
   1519                                   "start",
   1520                                   &po->of.start_row);
   1521   /* since protocol v12 */
   1522   TALER_MHD_parse_request_number (connection,
   1523                                   "offset",
   1524                                   &po->of.start_row);
   1525   if (INT64_MAX < po->of.start_row)
   1526   {
   1527     GNUNET_break_op (0);
   1528     return TALER_MHD_reply_with_error (connection,
   1529                                        MHD_HTTP_BAD_REQUEST,
   1530                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1531                                        "offset");
   1532   }
   1533   po->summary_filter = tr (MHD_lookup_connection_value (connection,
   1534                                                         MHD_GET_ARGUMENT_KIND,
   1535                                                         "summary_filter"));
   1536   po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */
   1537   po->of.session_id
   1538     = MHD_lookup_connection_value (connection,
   1539                                    MHD_GET_ARGUMENT_KIND,
   1540                                    "session_id");
   1541   po->of.fulfillment_url
   1542     = MHD_lookup_connection_value (connection,
   1543                                    MHD_GET_ARGUMENT_KIND,
   1544                                    "fulfillment_url");
   1545   TALER_MHD_parse_request_timeout (connection,
   1546                                    &po->long_poll_timeout);
   1547   if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout))
   1548   {
   1549     GNUNET_break_op (0);
   1550     return TALER_MHD_reply_with_error (connection,
   1551                                        MHD_HTTP_BAD_REQUEST,
   1552                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1553                                        "timeout_ms");
   1554   }
   1555   if ( (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) &&
   1556        (NULL == mi->po_eh) )
   1557   {
   1558     struct TMH_OrderChangeEventP change_eh = {
   1559       .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE),
   1560       .header.size = htons (sizeof (change_eh)),
   1561       .merchant_pub = mi->merchant_pub
   1562     };
   1563 
   1564     mi->po_eh = TALER_MERCHANTDB_event_listen (TMH_db,
   1565                                                &change_eh.header,
   1566                                                GNUNET_TIME_UNIT_FOREVER_REL,
   1567                                                &resume_by_event,
   1568                                                mi);
   1569   }
   1570 
   1571   po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout);
   1572 
   1573   qs = TALER_MERCHANTDB_lookup_orders (TMH_db,
   1574                                        po->instance_id,
   1575                                        &po->of,
   1576                                        &add_order,
   1577                                        po);
   1578   if (0 > qs)
   1579   {
   1580     GNUNET_break (0);
   1581     po->result = TALER_EC_GENERIC_DB_FETCH_FAILED;
   1582   }
   1583   if (TALER_EC_NONE != po->result)
   1584   {
   1585     GNUNET_break (0);
   1586     return TALER_MHD_reply_with_error (connection,
   1587                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
   1588                                        po->result,
   1589                                        NULL);
   1590   }
   1591   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
   1592        (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) )
   1593   {
   1594     GNUNET_assert (NULL == po->order_timeout_task);
   1595     po->order_timeout_task
   1596       = GNUNET_SCHEDULER_add_at (po->long_poll_timeout,
   1597                                  &order_timeout,
   1598                                  po);
   1599     GNUNET_CONTAINER_DLL_insert (mi->po_head,
   1600                                  mi->po_tail,
   1601                                  po);
   1602     po->in_dll = true;
   1603     MHD_suspend_connection (connection);
   1604     return MHD_YES;
   1605   }
   1606   return reply_orders (po,
   1607                        connection,
   1608                        mi);
   1609 }
   1610 
   1611 
   1612 /* end of taler-merchant-httpd_get-private-orders.c */