merchant

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

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


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