merchant

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

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


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