LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_private-post-orders.c (source / functions) Hit Total Coverage
Test: GNU Taler merchant coverage report Lines: 0 435 0.0 %
Date: 2022-06-30 06:15:34 Functions: 0 8 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   (C) 2014, 2015, 2016, 2018, 2020, 2021 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify
       6             :   it under the terms of the GNU Affero General Public License as
       7             :   published by the Free Software Foundation; either version 3,
       8             :   or (at your option) any later version.
       9             : 
      10             :   TALER is distributed in the hope that it will be useful, but
      11             :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :   GNU General Public License for more details.
      14             : 
      15             :   You should have received a copy of the GNU General Public
      16             :   License along with TALER; see the file COPYING.  If not,
      17             :   see <http://www.gnu.org/licenses/>
      18             : */
      19             : 
      20             : /**
      21             :  * @file taler-merchant-httpd_private-post-orders.c
      22             :  * @brief the POST /orders handler
      23             :  * @author Christian Grothoff
      24             :  * @author Marcello Stanisci
      25             :  */
      26             : #include "platform.h"
      27             : #include <jansson.h>
      28             : #include <taler/taler_signatures.h>
      29             : #include <taler/taler_json_lib.h>
      30             : #include "taler-merchant-httpd_private-post-orders.h"
      31             : #include "taler-merchant-httpd_auditors.h"
      32             : #include "taler-merchant-httpd_exchanges.h"
      33             : #include "taler-merchant-httpd_helper.h"
      34             : #include "taler-merchant-httpd_private-get-orders.h"
      35             : 
      36             : 
      37             : /**
      38             :  * How often do we retry the simple INSERT database transaction?
      39             :  */
      40             : #define MAX_RETRIES 3
      41             : 
      42             : /**
      43             :  * What is the label under which we find/place the merchant's
      44             :  * jurisdiction in the locations list by default?
      45             :  */
      46             : #define STANDARD_LABEL_MERCHANT_JURISDICTION "_mj"
      47             : 
      48             : /**
      49             :  * What is the label under which we find/place the merchant's
      50             :  * address in the locations list by default?
      51             :  */
      52             : #define STANDARD_LABEL_MERCHANT_ADDRESS "_ma"
      53             : 
      54             : 
      55             : /**
      56             :  * Check that the given JSON array of products is well-formed.
      57             :  *
      58             :  * @param products JSON array to check
      59             :  * @return #GNUNET_OK if all is fine
      60             :  */
      61             : static enum GNUNET_GenericReturnValue
      62           0 : check_products (const json_t *products)
      63             : {
      64             :   size_t index;
      65             :   json_t *value;
      66             : 
      67           0 :   if (! json_is_array (products))
      68             :   {
      69           0 :     GNUNET_break (0);
      70           0 :     return GNUNET_SYSERR;
      71             :   }
      72           0 :   json_array_foreach (products, index, value) {
      73             :     const char *description;
      74             :     const char *error_name;
      75             :     unsigned int error_line;
      76             :     enum GNUNET_GenericReturnValue res;
      77             :     struct GNUNET_JSON_Specification spec[] = {
      78             :       // FIXME: parse and format-validate all
      79             :       // optional fields of a product and check validity
      80           0 :       GNUNET_JSON_spec_string ("description",
      81             :                                &description),
      82           0 :       GNUNET_JSON_spec_end ()
      83             :     };
      84             : 
      85             :     /* extract fields we need to sign separately */
      86           0 :     res = GNUNET_JSON_parse (value,
      87             :                              spec,
      88             :                              &error_name,
      89             :                              &error_line);
      90           0 :     if (GNUNET_OK != res)
      91             :     {
      92           0 :       GNUNET_break (0);
      93           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
      94             :                   "Product parsing failed at #%u: %s:%u\n",
      95             :                   (unsigned int) index,
      96             :                   error_name,
      97             :                   error_line);
      98           0 :       return GNUNET_SYSERR;
      99             :     }
     100           0 :     GNUNET_JSON_parse_free (spec);
     101             :   }
     102           0 :   return GNUNET_OK;
     103             : }
     104             : 
     105             : 
     106             : /**
     107             :  * Generate the base URL for the given merchant instance.
     108             :  *
     109             :  * @param connection the MHD connection
     110             :  * @param instance_id the merchant instance ID
     111             :  * @returns the merchant instance's base URL
     112             :  */
     113             : static char *
     114           0 : make_merchant_base_url (struct MHD_Connection *connection,
     115             :                         const char *instance_id)
     116             : {
     117             :   const char *host;
     118             :   const char *forwarded_host;
     119             :   const char *uri_path;
     120           0 :   struct GNUNET_Buffer buf = { 0 };
     121             : 
     122           0 :   if (GNUNET_YES == TALER_mhd_is_https (connection))
     123           0 :     GNUNET_buffer_write_str (&buf, "https://");
     124             :   else
     125           0 :     GNUNET_buffer_write_str (&buf, "http://");
     126           0 :   host = MHD_lookup_connection_value (connection,
     127             :                                       MHD_HEADER_KIND,
     128             :                                       MHD_HTTP_HEADER_HOST);
     129           0 :   forwarded_host = MHD_lookup_connection_value (connection,
     130             :                                                 MHD_HEADER_KIND,
     131             :                                                 "X-Forwarded-Host");
     132           0 :   if (NULL != forwarded_host)
     133             :   {
     134           0 :     GNUNET_buffer_write_str (&buf,
     135             :                              forwarded_host);
     136             :   }
     137             :   else
     138             :   {
     139           0 :     GNUNET_assert (NULL != host);
     140           0 :     GNUNET_buffer_write_str (&buf,
     141             :                              host);
     142             :   }
     143           0 :   uri_path = MHD_lookup_connection_value (connection,
     144             :                                           MHD_HEADER_KIND,
     145             :                                           "X-Forwarded-Prefix");
     146           0 :   if (NULL != uri_path)
     147           0 :     GNUNET_buffer_write_path (&buf, uri_path);
     148             : 
     149           0 :   if (0 != strcmp (instance_id,
     150             :                    "default"))
     151             :   {
     152           0 :     GNUNET_buffer_write_path (&buf,
     153             :                               "/instances/");
     154           0 :     GNUNET_buffer_write_str (&buf,
     155             :                              instance_id);
     156             :   }
     157           0 :   GNUNET_buffer_write_path (&buf,
     158             :                             "");
     159           0 :   return GNUNET_buffer_reap_str (&buf);
     160             : }
     161             : 
     162             : 
     163             : /**
     164             :  * Information about a product we are supposed to add to the order
     165             :  * based on what we know it from our inventory.
     166             :  */
     167             : struct InventoryProduct
     168             : {
     169             :   /**
     170             :    * Identifier of the product in the inventory.
     171             :    */
     172             :   const char *product_id;
     173             : 
     174             :   /**
     175             :    * Number of units of the product to add to the order.
     176             :    */
     177             :   uint32_t quantity;
     178             : };
     179             : 
     180             : 
     181             : /**
     182             :  * Execute the database transaction to setup the order.
     183             :  *
     184             :  * @param hc handler context for the request
     185             :  * @param order_id unique ID for the order
     186             :  * @param h_post_data hash of the client's POST request, for idempotency checks
     187             :  * @param pay_deadline until when does the order have to be paid
     188             :  * @param[in] order order to process (not modified)
     189             :  * @param claim_token token to use for access control
     190             :  * @param inventory_products_length length of the @a inventory_products array
     191             :  * @param inventory_products array of products to add to @a order from our inventory
     192             :  * @param uuids_length length of the @a uuids array
     193             :  * @param uuids array of UUIDs used to reserve products from @a inventory_products
     194             :  * @param[out] out_of_stock_index which product (by offset) is out of stock, UINT_MAX if all were in-stock
     195             :  * @return transaction status, 0 if @a uuids were insufficient to reserve required inventory
     196             :  */
     197             : static enum GNUNET_DB_QueryStatus
     198           0 : execute_transaction (struct TMH_HandlerContext *hc,
     199             :                      const char *order_id,
     200             :                      const struct TALER_MerchantPostDataHashP *h_post_data,
     201             :                      struct GNUNET_TIME_Timestamp pay_deadline,
     202             :                      const json_t *order,
     203             :                      const struct TALER_ClaimTokenP *claim_token,
     204             :                      unsigned int inventory_products_length,
     205             :                      const struct InventoryProduct inventory_products[],
     206             :                      unsigned int uuids_length,
     207             :                      const struct GNUNET_Uuid uuids[],
     208             :                      unsigned int *out_of_stock_index)
     209             : {
     210             :   enum GNUNET_DB_QueryStatus qs;
     211             :   struct GNUNET_TIME_Timestamp timestamp;
     212             :   uint64_t order_serial;
     213             : 
     214           0 :   if (GNUNET_OK !=
     215           0 :       TMH_db->start (TMH_db->cls,
     216             :                      "insert_order"))
     217             :   {
     218           0 :     GNUNET_break (0);
     219           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     220             :   }
     221             :   /* Setup order */
     222           0 :   qs = TMH_db->insert_order (TMH_db->cls,
     223           0 :                              hc->instance->settings.id,
     224             :                              order_id,
     225             :                              h_post_data,
     226             :                              pay_deadline,
     227             :                              claim_token,
     228             :                              order); // called 'contract terms' at database.
     229           0 :   if (qs <= 0)
     230             :   {
     231             :     /* qs == 0: probably instance does not exist (anymore) */
     232           0 :     TMH_db->rollback (TMH_db->cls);
     233           0 :     return qs;
     234             :   }
     235             :   /* Migrate locks from UUIDs to new order: first release old locks */
     236           0 :   for (unsigned int i = 0; i<uuids_length; i++)
     237             :   {
     238           0 :     qs = TMH_db->unlock_inventory (TMH_db->cls,
     239           0 :                                    &uuids[i]);
     240           0 :     if (qs < 0)
     241             :     {
     242           0 :       TMH_db->rollback (TMH_db->cls);
     243           0 :       return qs;
     244             :     }
     245             :     /* qs == 0 is OK here, that just means we did not HAVE any lock under this
     246             :        UUID */
     247             :   }
     248             :   /* Migrate locks from UUIDs to new order: acquire new locks
     249             :      (note: this can basically ONLY fail on serializability OR
     250             :      because the UUID locks were insufficient for the desired
     251             :      quantities). */
     252           0 :   for (unsigned int i = 0; i<inventory_products_length; i++)
     253             :   {
     254           0 :     qs = TMH_db->insert_order_lock (TMH_db->cls,
     255           0 :                                     hc->instance->settings.id,
     256             :                                     order_id,
     257           0 :                                     inventory_products[i].product_id,
     258           0 :                                     inventory_products[i].quantity);
     259           0 :     if (qs < 0)
     260             :     {
     261           0 :       TMH_db->rollback (TMH_db->cls);
     262           0 :       return qs;
     263             :     }
     264           0 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     265             :     {
     266             :       /* qs == 0: lock acquisition failed due to insufficient stocks */
     267           0 :       TMH_db->rollback (TMH_db->cls);
     268           0 :       *out_of_stock_index = i; /* indicate which product is causing the issue */
     269           0 :       return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     270             :     }
     271             :   }
     272           0 :   *out_of_stock_index = UINT_MAX;
     273             : 
     274             :   /* Get the order serial and timestamp for the order we just created to
     275             :      update long-poll clients. */
     276           0 :   qs = TMH_db->lookup_order_summary (TMH_db->cls,
     277           0 :                                      hc->instance->settings.id,
     278             :                                      order_id,
     279             :                                      &timestamp,
     280             :                                      &order_serial);
     281           0 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
     282             :   {
     283           0 :     TMH_db->rollback (TMH_db->cls);
     284           0 :     return qs;
     285             :   }
     286           0 :   TMH_notify_order_change (hc->instance,
     287             :                            TMH_OSF_NONE,
     288             :                            timestamp,
     289             :                            order_serial);
     290             :   /* finally, commit transaction (note: if it fails, we ALSO re-acquire
     291             :      the UUID locks, which is exactly what we want) */
     292           0 :   qs = TMH_db->commit (TMH_db->cls);
     293           0 :   if (0 > qs)
     294           0 :     return qs;
     295           0 :   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;   /* 1 == success! */
     296             : }
     297             : 
     298             : 
     299             : /**
     300             :  * Transform an order into a proposal and store it in the
     301             :  * database. Write the resulting proposal or an error message
     302             :  * of a MHD connection.
     303             :  *
     304             :  * @param connection connection to write the result or error to
     305             :  * @param hc handler context for the request
     306             :  * @param h_post_data hash of the client's POST request, for idempotency checks
     307             :  * @param[in,out] order order to process (can be modified)
     308             :  * @param claim_token token to use for access control
     309             :  * @param inventory_products_length length of the @a inventory_products array
     310             :  * @param inventory_products array of products to add to @a order from our inventory
     311             :  * @param uuids_length length of the @a uuids array
     312             :  * @param uuids array of UUIDs used to reserve products from @a inventory_products
     313             :  * @return MHD result code
     314             :  */
     315             : static MHD_RESULT
     316           0 : execute_order (struct MHD_Connection *connection,
     317             :                struct TMH_HandlerContext *hc,
     318             :                const struct TALER_MerchantPostDataHashP *h_post_data,
     319             :                json_t *order,
     320             :                const struct TALER_ClaimTokenP *claim_token,
     321             :                unsigned int inventory_products_length,
     322             :                const struct InventoryProduct inventory_products[],
     323             :                unsigned int uuids_length,
     324             :                const struct GNUNET_Uuid uuids[])
     325             : {
     326           0 :   const struct TALER_MERCHANTDB_InstanceSettings *settings =
     327           0 :     &hc->instance->settings;
     328             :   struct TALER_Amount total;
     329             :   const char *order_id;
     330             :   const char *summary;
     331           0 :   const char *fulfillment_msg = NULL;
     332             :   json_t *products;
     333             :   json_t *merchant;
     334           0 :   json_t *summary_i18n = NULL;
     335           0 :   json_t *fulfillment_i18n = NULL;
     336             :   struct GNUNET_TIME_Timestamp timestamp;
     337           0 :   struct GNUNET_TIME_Timestamp refund_deadline = { 0 };
     338             :   struct GNUNET_TIME_Timestamp wire_transfer_deadline;
     339             :   struct GNUNET_TIME_Timestamp pay_deadline;
     340             :   struct GNUNET_JSON_Specification spec[] = {
     341           0 :     TALER_JSON_spec_amount ("amount",
     342             :                             TMH_currency,
     343             :                             &total),
     344           0 :     GNUNET_JSON_spec_string ("order_id",
     345             :                              &order_id),
     346           0 :     GNUNET_JSON_spec_string ("summary",
     347             :                              &summary),
     348             :     /**
     349             :      * The following entries we don't actually need,
     350             :      * except to check that the order is well-formed */
     351           0 :     GNUNET_JSON_spec_json ("products",
     352             :                            &products),
     353           0 :     GNUNET_JSON_spec_json ("merchant",
     354             :                            &merchant),
     355           0 :     GNUNET_JSON_spec_mark_optional (
     356             :       GNUNET_JSON_spec_json ("summary_i18n",
     357             :                              &summary_i18n),
     358             :       NULL),
     359           0 :     GNUNET_JSON_spec_mark_optional (
     360             :       GNUNET_JSON_spec_string ("fulfillment_message",
     361             :                                &fulfillment_msg),
     362             :       NULL),
     363           0 :     GNUNET_JSON_spec_mark_optional (
     364             :       GNUNET_JSON_spec_json ("fulfillment_message_i18n",
     365             :                              &fulfillment_i18n),
     366             :       NULL),
     367           0 :     GNUNET_JSON_spec_timestamp ("timestamp",
     368             :                                 &timestamp),
     369           0 :     GNUNET_JSON_spec_mark_optional (
     370             :       GNUNET_JSON_spec_timestamp ("refund_deadline",
     371             :                                   &refund_deadline),
     372             :       NULL),
     373           0 :     GNUNET_JSON_spec_timestamp ("pay_deadline",
     374             :                                 &pay_deadline),
     375           0 :     GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
     376             :                                 &wire_transfer_deadline),
     377           0 :     GNUNET_JSON_spec_end ()
     378             :   };
     379             :   enum GNUNET_DB_QueryStatus qs;
     380             :   unsigned int out_of_stock_index;
     381             : 
     382             :   /* extract fields we need to sign separately */
     383             :   {
     384             :     enum GNUNET_GenericReturnValue res;
     385             : 
     386           0 :     res = TALER_MHD_parse_json_data (connection,
     387             :                                      order,
     388             :                                      spec);
     389           0 :     if (GNUNET_OK != res)
     390             :     {
     391           0 :       GNUNET_break_op (0);
     392             :       return (GNUNET_NO == res)
     393             :              ? MHD_YES
     394           0 :              : MHD_NO;
     395             :     }
     396             :   }
     397             : 
     398             :   /* check product list in contract is well-formed */
     399           0 :   if (GNUNET_OK != check_products (products))
     400             :   {
     401           0 :     GNUNET_JSON_parse_free (spec);
     402           0 :     return TALER_MHD_reply_with_error (connection,
     403             :                                        MHD_HTTP_BAD_REQUEST,
     404             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     405             :                                        "order:products");
     406             :   }
     407             : 
     408           0 :   if ( (NULL != fulfillment_i18n) &&
     409           0 :        (! TALER_JSON_check_i18n (fulfillment_i18n)) )
     410             :   {
     411           0 :     GNUNET_JSON_parse_free (spec);
     412           0 :     return TALER_MHD_reply_with_error (connection,
     413             :                                        MHD_HTTP_BAD_REQUEST,
     414             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     415             :                                        "order:fulfillment_message_i18n");
     416             :   }
     417           0 :   if ( (NULL != summary_i18n) &&
     418           0 :        (! TALER_JSON_check_i18n (summary_i18n)) )
     419             :   {
     420           0 :     GNUNET_JSON_parse_free (spec);
     421           0 :     return TALER_MHD_reply_with_error (connection,
     422             :                                        MHD_HTTP_BAD_REQUEST,
     423             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
     424             :                                        "order:summary_i18n");
     425             :   }
     426             : 
     427             :   /* Test if we already have an order with this id */
     428             :   {
     429             :     struct TALER_ClaimTokenP token;
     430             :     json_t *contract_terms;
     431             :     struct TALER_MerchantPostDataHashP orig_post;
     432             : 
     433           0 :     TMH_db->preflight (TMH_db->cls);
     434           0 :     qs = TMH_db->lookup_order (TMH_db->cls,
     435           0 :                                hc->instance->settings.id,
     436             :                                order_id,
     437             :                                &token,
     438             :                                &orig_post,
     439             :                                &contract_terms);
     440             :     /* If yes, check for idempotency */
     441           0 :     if (0 > qs)
     442             :     {
     443           0 :       GNUNET_break (0);
     444           0 :       TMH_db->rollback (TMH_db->cls);
     445           0 :       GNUNET_JSON_parse_free (spec);
     446           0 :       return TALER_MHD_reply_with_error (connection,
     447             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     448             :                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
     449             :                                          "lookup_order");
     450             :     }
     451           0 :     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     452             :     {
     453             :       MHD_RESULT ret;
     454             : 
     455           0 :       json_decref (contract_terms);
     456             :       /* Comparing the contract terms is sufficient because all the other
     457             :          params get added to it at some point. */
     458           0 :       if (0 == GNUNET_memcmp (&orig_post,
     459             :                               h_post_data))
     460             :       {
     461           0 :         ret = TALER_MHD_REPLY_JSON_PACK (
     462             :           connection,
     463             :           MHD_HTTP_OK,
     464             :           GNUNET_JSON_pack_string ("order_id",
     465             :                                    order_id),
     466             :           GNUNET_JSON_pack_allow_null (
     467             :             GNUNET_JSON_pack_data_varsize (
     468             :               "token",
     469             :               GNUNET_is_zero (&token)
     470             :               ? NULL
     471             :               : &token,
     472             :               sizeof (token))));
     473             :       }
     474             :       else
     475             :       {
     476             :         /* This request is not idempotent */
     477           0 :         ret = TALER_MHD_reply_with_error (
     478             :           connection,
     479             :           MHD_HTTP_CONFLICT,
     480             :           TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
     481             :           order_id);
     482             :       }
     483           0 :       GNUNET_JSON_parse_free (spec);
     484           0 :       return ret;
     485             :     }
     486             :   }
     487           0 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     488             :               "Executing database transaction to create order '%s' for instance '%s'\n",
     489             :               order_id,
     490             :               settings->id);
     491           0 :   for (unsigned int i = 0; i<MAX_RETRIES; i++)
     492             :   {
     493           0 :     TMH_db->preflight (TMH_db->cls);
     494           0 :     qs = execute_transaction (hc,
     495             :                               order_id,
     496             :                               h_post_data,
     497             :                               pay_deadline,
     498             :                               order,
     499             :                               claim_token,
     500             :                               inventory_products_length,
     501             :                               inventory_products,
     502             :                               uuids_length,
     503             :                               uuids,
     504             :                               &out_of_stock_index);
     505           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     506           0 :       break;
     507             :   }
     508           0 :   if (0 >= qs)
     509             :   {
     510           0 :     GNUNET_JSON_parse_free (spec);
     511             :     /* Special report if retries insufficient */
     512           0 :     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     513             :     {
     514           0 :       GNUNET_break (0);
     515           0 :       return TALER_MHD_reply_with_error (connection,
     516             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
     517             :                                          TALER_EC_GENERIC_DB_SOFT_FAILURE,
     518             :                                          NULL);
     519             :     }
     520           0 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     521             :     {
     522             :       /* should be: contract (!) with same order ID
     523             :          already exists */
     524           0 :       return TALER_MHD_reply_with_error (
     525             :         connection,
     526             :         MHD_HTTP_CONFLICT,
     527             :         TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
     528             :         order_id);
     529             :     }
     530             :     /* Other hard transaction error (disk full, etc.) */
     531           0 :     GNUNET_break (0);
     532           0 :     return TALER_MHD_reply_with_error (
     533             :       connection,
     534             :       MHD_HTTP_INTERNAL_SERVER_ERROR,
     535             :       TALER_EC_GENERIC_DB_COMMIT_FAILED,
     536             :       NULL);
     537             :   }
     538             : 
     539             :   /* DB transaction succeeded, check for out-of-stock */
     540           0 :   if (out_of_stock_index < UINT_MAX)
     541             :   {
     542             :     /* We had a product that has insufficient quantities,
     543             :        generate the details for the response. */
     544             :     struct TALER_MERCHANTDB_ProductDetails pd;
     545             :     MHD_RESULT ret;
     546             : 
     547           0 :     memset (&pd, 0, sizeof (pd));
     548           0 :     qs = TMH_db->lookup_product (
     549           0 :       TMH_db->cls,
     550           0 :       hc->instance->settings.id,
     551           0 :       inventory_products[out_of_stock_index].product_id,
     552             :       &pd);
     553           0 :     GNUNET_JSON_parse_free (spec);
     554           0 :     switch (qs)
     555             :     {
     556           0 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     557           0 :       ret = TALER_MHD_REPLY_JSON_PACK (
     558             :         connection,
     559             :         MHD_HTTP_GONE,
     560             :         GNUNET_JSON_pack_string (
     561             :           "product_id",
     562             :           inventory_products[out_of_stock_index].product_id),
     563             :         GNUNET_JSON_pack_uint64 (
     564             :           "requested_quantity",
     565             :           inventory_products[out_of_stock_index].quantity),
     566             :         GNUNET_JSON_pack_uint64 (
     567             :           "available_quantity",
     568             :           pd.total_stock - pd.total_sold - pd.total_lost),
     569             :         GNUNET_JSON_pack_allow_null (
     570             :           GNUNET_JSON_pack_timestamp (
     571             :             "restock_expected",
     572             :             pd.next_restock)));
     573           0 :       TALER_MERCHANTDB_product_details_free (&pd);
     574           0 :       return ret;
     575           0 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     576           0 :       return TALER_MHD_REPLY_JSON_PACK (
     577             :         connection,
     578             :         MHD_HTTP_GONE,
     579             :         GNUNET_JSON_pack_string (
     580             :           "product_id",
     581             :           inventory_products[out_of_stock_index].product_id),
     582             :         GNUNET_JSON_pack_uint64 (
     583             :           "requested_quantity",
     584             :           inventory_products[out_of_stock_index].quantity),
     585             :         GNUNET_JSON_pack_uint64 (
     586             :           "available_quantity",
     587             :           0));
     588           0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     589           0 :       GNUNET_break (0);
     590           0 :       return TALER_MHD_reply_with_error (
     591             :         connection,
     592             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     593             :         TALER_EC_GENERIC_DB_SOFT_FAILURE,
     594             :         NULL);
     595           0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     596           0 :       return TALER_MHD_reply_with_error (
     597             :         connection,
     598             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     599             :         TALER_EC_GENERIC_DB_FETCH_FAILED,
     600             :         NULL);
     601             :     }
     602           0 :     GNUNET_break (0);
     603           0 :     return MHD_NO;
     604             :   }
     605             : 
     606             :   /* Everything in-stock, generate positive response */
     607             :   {
     608             :     MHD_RESULT ret;
     609             : 
     610           0 :     ret = TALER_MHD_REPLY_JSON_PACK (
     611             :       connection,
     612             :       MHD_HTTP_OK,
     613             :       GNUNET_JSON_pack_string ("order_id",
     614             :                                order_id),
     615             :       GNUNET_JSON_pack_allow_null (
     616             :         GNUNET_JSON_pack_data_varsize (
     617             :           "token",
     618             :           GNUNET_is_zero (claim_token)
     619             :           ? NULL
     620             :           : claim_token,
     621             :           sizeof (*claim_token))));
     622           0 :     GNUNET_JSON_parse_free (spec);
     623           0 :     return ret;
     624             :   }
     625             : }
     626             : 
     627             : 
     628             : /**
     629             :  * Add missing fields to the order.  Upon success, continue
     630             :  * processing with execute_order().
     631             :  *
     632             :  * @param connection connection to write the result or error to
     633             :  * @param hc handler context for the request
     634             :  * @param h_post_data hash of the client's POST request, for idempotency checks
     635             :  * @param[in,out] order order to process (can be modified)
     636             :  * @param claim_token token to use for access control
     637             :  * @param refund_delay refund delay
     638             :  * @param inventory_products_length length of the @a inventory_products array
     639             :  * @param inventory_products array of products to add to @a order from our inventory
     640             :  * @param uuids_length length of the @a uuids array
     641             :  * @param uuids array of UUIDs used to reserve products from @a inventory_products
     642             :  * @return MHD result code
     643             :  */
     644             : static MHD_RESULT
     645           0 : patch_order (struct MHD_Connection *connection,
     646             :              struct TMH_HandlerContext *hc,
     647             :              const struct TALER_MerchantPostDataHashP *h_post_data,
     648             :              json_t *order,
     649             :              const struct TALER_ClaimTokenP *claim_token,
     650             :              struct GNUNET_TIME_Relative refund_delay,
     651             :              unsigned int inventory_products_length,
     652             :              const struct InventoryProduct inventory_products[],
     653             :              unsigned int uuids_length,
     654             :              const struct GNUNET_Uuid uuids[])
     655             : {
     656           0 :   const struct TALER_MERCHANTDB_InstanceSettings *settings =
     657           0 :     &hc->instance->settings;
     658           0 :   const char *order_id = NULL;
     659           0 :   const char *fulfillment_url = NULL;
     660           0 :   const char *merchant_base_url = NULL;
     661           0 :   json_t *jmerchant = NULL;
     662           0 :   json_t *delivery_location = NULL;
     663           0 :   struct TALER_Amount max_wire_fee = { 0 };
     664           0 :   struct TALER_Amount max_fee = { 0 };
     665           0 :   uint32_t wire_fee_amortization = 0;
     666           0 :   struct GNUNET_TIME_Timestamp timestamp
     667             :     = GNUNET_TIME_UNIT_ZERO_TS;
     668           0 :   struct GNUNET_TIME_Timestamp delivery_date
     669             :     = GNUNET_TIME_UNIT_ZERO_TS;
     670           0 :   struct GNUNET_TIME_Timestamp refund_deadline
     671             :     = GNUNET_TIME_UNIT_FOREVER_TS;
     672           0 :   struct GNUNET_TIME_Timestamp pay_deadline
     673             :     = GNUNET_TIME_UNIT_ZERO_TS;
     674           0 :   struct GNUNET_TIME_Timestamp wire_deadline
     675             :     = GNUNET_TIME_UNIT_FOREVER_TS;
     676             :   /* auto_refund only needs to be type-checked,
     677             :    * mostly because in GNUnet relative times can't
     678             :    * be negative.  */
     679             :   struct GNUNET_TIME_Relative auto_refund;
     680             :   struct GNUNET_JSON_Specification spec[] = {
     681           0 :     GNUNET_JSON_spec_mark_optional (
     682             :       GNUNET_JSON_spec_string ("merchant_base_url",
     683             :                                &merchant_base_url),
     684             :       NULL),
     685           0 :     GNUNET_JSON_spec_mark_optional (
     686             :       GNUNET_JSON_spec_json ("merchant",
     687             :                              &jmerchant),
     688             :       NULL),
     689           0 :     GNUNET_JSON_spec_mark_optional (
     690             :       GNUNET_JSON_spec_string ("order_id",
     691             :                                &order_id),
     692             :       NULL),
     693           0 :     GNUNET_JSON_spec_mark_optional (
     694             :       GNUNET_JSON_spec_string ("fulfillment_url",
     695             :                                &fulfillment_url),
     696             :       NULL),
     697           0 :     GNUNET_JSON_spec_mark_optional (
     698             :       GNUNET_JSON_spec_timestamp ("timestamp",
     699             :                                   &timestamp),
     700             :       NULL),
     701           0 :     GNUNET_JSON_spec_mark_optional (
     702             :       GNUNET_JSON_spec_timestamp ("refund_deadline",
     703             :                                   &refund_deadline),
     704             :       NULL),
     705           0 :     GNUNET_JSON_spec_mark_optional (
     706             :       GNUNET_JSON_spec_timestamp ("pay_deadline",
     707             :                                   &pay_deadline),
     708             :       NULL),
     709           0 :     GNUNET_JSON_spec_mark_optional (
     710             :       GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
     711             :                                   &wire_deadline),
     712             :       NULL),
     713           0 :     GNUNET_JSON_spec_mark_optional (
     714             :       TALER_JSON_spec_amount ("max_fee",
     715             :                               TMH_currency,
     716             :                               &max_fee),
     717             :       NULL),
     718           0 :     GNUNET_JSON_spec_mark_optional (
     719             :       TALER_JSON_spec_amount ("max_wire_fee",
     720             :                               TMH_currency,
     721             :                               &max_wire_fee),
     722             :       NULL),
     723           0 :     GNUNET_JSON_spec_mark_optional (
     724             :       GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
     725             :                                &wire_fee_amortization),
     726             :       NULL),
     727           0 :     GNUNET_JSON_spec_mark_optional (
     728             :       GNUNET_JSON_spec_timestamp ("delivery_date",
     729             :                                   &delivery_date),
     730             :       NULL),
     731           0 :     GNUNET_JSON_spec_mark_optional (
     732             :       GNUNET_JSON_spec_relative_time ("auto_refund",
     733             :                                       &auto_refund),
     734             :       NULL),
     735           0 :     GNUNET_JSON_spec_mark_optional (
     736             :       GNUNET_JSON_spec_json ("delivery_location",
     737             :                              &delivery_location),
     738             :       NULL),
     739           0 :     GNUNET_JSON_spec_end ()
     740             :   };
     741             :   enum GNUNET_GenericReturnValue ret;
     742             : 
     743           0 :   ret = TALER_MHD_parse_json_data (connection,
     744             :                                    order,
     745             :                                    spec);
     746           0 :   if (GNUNET_OK != ret)
     747             :   {
     748           0 :     GNUNET_break_op (0);
     749             :     return (GNUNET_NO == ret)
     750             :            ? MHD_YES
     751           0 :            : MHD_NO;
     752             :   }
     753             : 
     754             :   /* Add order_id if it doesn't exist. */
     755           0 :   if (NULL == order_id)
     756             :   {
     757             :     char buf[256];
     758             :     time_t timer;
     759             :     struct tm *tm_info;
     760             :     size_t off;
     761             :     uint64_t rand;
     762             :     char *last;
     763             :     json_t *jbuf;
     764             : 
     765           0 :     time (&timer);
     766           0 :     tm_info = localtime (&timer);
     767           0 :     if (NULL == tm_info)
     768             :     {
     769           0 :       return TALER_MHD_reply_with_error (
     770             :         connection,
     771             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     772             :         TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME,
     773             :         NULL);
     774             :     }
     775           0 :     off = strftime (buf,
     776             :                     sizeof (buf) - 1,
     777             :                     "%Y.%j",
     778             :                     tm_info);
     779             :     /* Check for error state of strftime */
     780           0 :     GNUNET_assert (0 != off);
     781           0 :     buf[off++] = '-';
     782           0 :     rand = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
     783             :                                      UINT64_MAX);
     784           0 :     last = GNUNET_STRINGS_data_to_string (&rand,
     785             :                                           sizeof (uint64_t),
     786             :                                           &buf[off],
     787             :                                           sizeof (buf) - off);
     788           0 :     GNUNET_assert (NULL != last);
     789           0 :     *last = '\0';
     790           0 :     jbuf = json_string (buf);
     791           0 :     GNUNET_assert (NULL != jbuf);
     792           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     793             :                 "Assigning order ID `%s' server-side\n",
     794             :                 buf);
     795           0 :     GNUNET_break (0 ==
     796             :                   json_object_set_new (order,
     797             :                                        "order_id",
     798             :                                        jbuf));
     799           0 :     order_id = json_string_value (jbuf);
     800           0 :     GNUNET_assert (NULL != order_id);
     801             :   }
     802             : 
     803             :   /* Patch fulfillment URL with order_id (implements #6467). */
     804           0 :   if (NULL != fulfillment_url)
     805             :   {
     806             :     const char *pos;
     807             : 
     808           0 :     pos = strstr (fulfillment_url,
     809             :                   "${ORDER_ID}");
     810           0 :     if (NULL != pos)
     811             :     {
     812             :       /* replace ${ORDER_ID} with the real order_id */
     813             :       char *nurl;
     814             : 
     815             :       /* We only allow one placeholder */
     816           0 :       if (strstr (pos + strlen ("${ORDER_ID}"),
     817             :                   "${ORDER_ID}"))
     818             :       {
     819             :         /* FIXME: free anything? */
     820           0 :         GNUNET_break_op (0);
     821           0 :         return TALER_MHD_reply_with_error (connection,
     822             :                                            MHD_HTTP_BAD_REQUEST,
     823             :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
     824             :                                            "fulfillment_url");
     825             :       }
     826             : 
     827           0 :       GNUNET_asprintf (&nurl,
     828             :                        "%.*s%s%s",
     829             :                        /* first output URL until ${ORDER_ID} */
     830           0 :                        (int) (pos - fulfillment_url),
     831             :                        fulfillment_url,
     832             :                        /* replace ${ORDER_ID} with the right order_id */
     833             :                        order_id,
     834             :                        /* append rest of original URL */
     835             :                        pos + strlen ("${ORDER_ID}"));
     836             :       /* replace in JSON of the order */
     837           0 :       GNUNET_break (0 ==
     838             :                     json_object_set_new (order,
     839             :                                          "fulfillment_url",
     840             :                                          json_string (nurl)));
     841           0 :       GNUNET_free (nurl);
     842             :     }
     843             :   }
     844             : 
     845             :   /* Check soundness of refund deadline, and that a timestamp
     846             :    * is actually present.  */
     847             :   {
     848           0 :     struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
     849             : 
     850             :     /* Add timestamp if it doesn't exist (or is zero) */
     851           0 :     if (GNUNET_TIME_absolute_is_zero (timestamp.abs_time))
     852             :     {
     853           0 :       GNUNET_assert (0 ==
     854             :                      json_object_set_new (order,
     855             :                                           "timestamp",
     856             :                                           GNUNET_JSON_from_timestamp (now)));
     857             :     }
     858             : 
     859             :     /* If no refund_deadline given, set one based on refund_delay.  */
     860           0 :     if (GNUNET_TIME_absolute_is_never (refund_deadline.abs_time))
     861             :     {
     862           0 :       if (GNUNET_TIME_relative_is_zero (refund_delay))
     863             :       {
     864           0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     865             :                     "Refund delay is zero, no refunds are possible for this order\n");
     866           0 :         refund_deadline = now; /* if delay was 0, ensure that refund_deadline == timestamp */
     867             :       }
     868             :       else
     869             :       {
     870           0 :         refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_delay);
     871             :       }
     872             : 
     873           0 :       GNUNET_assert (0 ==
     874             :                      json_object_set_new (order,
     875             :                                           "refund_deadline",
     876             :                                           GNUNET_JSON_from_timestamp (
     877             :                                             refund_deadline)));
     878             :     }
     879           0 :     if ( (! GNUNET_TIME_absolute_is_zero (delivery_date.abs_time)) &&
     880           0 :          (GNUNET_TIME_timestamp_cmp (delivery_date,
     881             :                                      <,
     882             :                                      now)) )
     883             :     {
     884           0 :       GNUNET_break_op (0);
     885           0 :       return TALER_MHD_reply_with_error (
     886             :         connection,
     887             :         MHD_HTTP_BAD_REQUEST,
     888             :         TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST,
     889             :         NULL);
     890             :     }
     891             :   }
     892             : 
     893           0 :   if (GNUNET_TIME_absolute_is_zero (pay_deadline.abs_time))
     894             :   {
     895             :     struct GNUNET_TIME_Timestamp t;
     896             : 
     897           0 :     t = GNUNET_TIME_relative_to_timestamp (settings->default_pay_delay);
     898           0 :     GNUNET_assert (0 ==
     899             :                    json_object_set_new (order,
     900             :                                         "pay_deadline",
     901             :                                         GNUNET_JSON_from_timestamp (t)));
     902             :   }
     903             : 
     904           0 :   if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
     905             :   {
     906             :     struct GNUNET_TIME_Timestamp t;
     907             : 
     908           0 :     t = GNUNET_TIME_relative_to_timestamp (
     909             :       GNUNET_TIME_relative_max (settings->default_wire_transfer_delay,
     910             :                                 refund_delay));
     911           0 :     wire_deadline = GNUNET_TIME_timestamp_max (refund_deadline,
     912             :                                                t);
     913           0 :     if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
     914             :     {
     915           0 :       GNUNET_break_op (0);
     916           0 :       return TALER_MHD_reply_with_error (
     917             :         connection,
     918             :         MHD_HTTP_BAD_REQUEST,
     919             :         TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER,
     920             :         "order:wire_transfer_deadline");
     921             : 
     922             :     }
     923           0 :     GNUNET_assert (0 ==
     924             :                    json_object_set_new (order,
     925             :                                         "wire_transfer_deadline",
     926             :                                         GNUNET_JSON_from_timestamp (
     927             :                                           wire_deadline)));
     928             :   }
     929           0 :   if (GNUNET_TIME_timestamp_cmp (wire_deadline,
     930             :                                  <,
     931             :                                  refund_deadline))
     932             :   {
     933           0 :     GNUNET_break_op (0);
     934           0 :     return TALER_MHD_reply_with_error (
     935             :       connection,
     936             :       MHD_HTTP_BAD_REQUEST,
     937             :       TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE,
     938             :       "order:wire_transfer_deadline;order:refund_deadline");
     939             :   }
     940             : 
     941             :   /* Note: total amount currency match checked
     942             :      later in execute_order() */
     943           0 :   if (GNUNET_OK !=
     944           0 :       TALER_amount_is_valid (&max_wire_fee))
     945             :   {
     946           0 :     GNUNET_assert (0 ==
     947             :                    json_object_set_new (
     948             :                      order,
     949             :                      "max_wire_fee",
     950             :                      TALER_JSON_from_amount (&settings->default_max_wire_fee)));
     951             :   }
     952             : 
     953           0 :   if (GNUNET_OK !=
     954           0 :       TALER_amount_is_valid (&max_fee))
     955             :   {
     956           0 :     GNUNET_assert (0 ==
     957             :                    json_object_set_new (
     958             :                      order,
     959             :                      "max_fee",
     960             :                      TALER_JSON_from_amount (
     961             :                        &settings->default_max_deposit_fee)));
     962             :   }
     963           0 :   if (0 == wire_fee_amortization)
     964             :   {
     965           0 :     GNUNET_assert (0 ==
     966             :                    json_object_set_new (
     967             :                      order,
     968             :                      "wire_fee_amortization",
     969             :                      json_integer (
     970             :                        (json_int_t) settings->default_wire_fee_amortization)));
     971             :   }
     972           0 :   if (NULL == merchant_base_url)
     973             :   {
     974             :     char *url;
     975             : 
     976           0 :     url = make_merchant_base_url (connection,
     977           0 :                                   settings->id);
     978           0 :     GNUNET_assert (0 ==
     979             :                    json_object_set_new (order,
     980             :                                         "merchant_base_url",
     981             :                                         json_string (url)));
     982           0 :     GNUNET_free (url);
     983             :   }
     984           0 :   else if (('\0' == *merchant_base_url) ||
     985           0 :            ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
     986             :   {
     987           0 :     GNUNET_break_op (0);
     988           0 :     return TALER_MHD_reply_with_error (
     989             :       connection,
     990             :       MHD_HTTP_BAD_REQUEST,
     991             :       TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
     992             :       "merchant_base_url is not valid");
     993             :   }
     994             : 
     995             :   /* Fill in merchant information if necessary */
     996           0 :   if (NULL != jmerchant)
     997             :   {
     998           0 :     GNUNET_break_op (0);
     999           0 :     return TALER_MHD_reply_with_error (
    1000             :       connection,
    1001             :       MHD_HTTP_BAD_REQUEST,
    1002             :       TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
    1003             :       "'merchant' field already set, but must be provided by backend");
    1004             :   }
    1005           0 :   jmerchant = GNUNET_JSON_PACK (
    1006             :     GNUNET_JSON_pack_string ("name",
    1007             :                              settings->name),
    1008             :     GNUNET_JSON_pack_allow_null (
    1009             :       GNUNET_JSON_pack_string ("website",
    1010             :                                settings->website)),
    1011             :     GNUNET_JSON_pack_allow_null (
    1012             :       GNUNET_JSON_pack_string ("email",
    1013             :                                settings->email)),
    1014             :     GNUNET_JSON_pack_allow_null (
    1015             :       GNUNET_JSON_pack_string ("logo",
    1016             :                                settings->logo)));
    1017           0 :   GNUNET_assert (NULL != jmerchant);
    1018             :   {
    1019             :     json_t *loca;
    1020             : 
    1021             :     /* Handle merchant address */
    1022           0 :     loca = settings->address;
    1023           0 :     if (NULL != loca)
    1024             :     {
    1025           0 :       loca = json_deep_copy (loca);
    1026           0 :       GNUNET_assert (0 ==
    1027             :                      json_object_set_new (jmerchant,
    1028             :                                           "address",
    1029             :                                           loca));
    1030             :     }
    1031             :   }
    1032             :   {
    1033             :     json_t *locj;
    1034             : 
    1035             :     /* Handle merchant jurisdiction */
    1036           0 :     locj = settings->jurisdiction;
    1037           0 :     if (NULL != locj)
    1038             :     {
    1039           0 :       locj = json_deep_copy (locj);
    1040           0 :       GNUNET_assert (0 ==
    1041             :                      json_object_set_new (jmerchant,
    1042             :                                           "jurisdiction",
    1043             :                                           locj));
    1044             :     }
    1045             :   }
    1046           0 :   GNUNET_assert (0 ==
    1047             :                  json_object_set_new (order,
    1048             :                                       "merchant",
    1049             :                                       jmerchant));
    1050             : 
    1051             :   /* add fields to the contract that the backend should provide */
    1052           0 :   GNUNET_assert (0 ==
    1053             :                  json_object_set (order,
    1054             :                                   "exchanges",
    1055             :                                   TMH_trusted_exchanges));
    1056           0 :   GNUNET_assert (0 ==
    1057             :                  json_object_set (order,
    1058             :                                   "auditors",
    1059             :                                   j_auditors));
    1060           0 :   GNUNET_assert (0 ==
    1061             :                  json_object_set_new (order,
    1062             :                                       "merchant_pub",
    1063             :                                       GNUNET_JSON_from_data_auto (
    1064             :                                         &hc->instance->merchant_pub)));
    1065           0 :   if (GNUNET_OK !=
    1066           0 :       TALER_JSON_contract_seed_forgettable (order))
    1067             :   {
    1068           0 :     return TALER_MHD_reply_with_error (
    1069             :       connection,
    1070             :       MHD_HTTP_BAD_REQUEST,
    1071             :       TALER_EC_GENERIC_JSON_INVALID,
    1072             :       "could not compute hash of order due to bogus forgettable fields");
    1073             :   }
    1074             : 
    1075           0 :   if ( (NULL != delivery_location) &&
    1076           0 :        (! TMH_location_object_valid (delivery_location)) )
    1077             :   {
    1078           0 :     GNUNET_break_op (0);
    1079           0 :     GNUNET_JSON_parse_free (spec);
    1080           0 :     return TALER_MHD_reply_with_error (connection,
    1081             :                                        MHD_HTTP_BAD_REQUEST,
    1082             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1083             :                                        "delivery_location");
    1084             :   }
    1085             : 
    1086             :   /* sanity check result */
    1087             :   {
    1088             :     struct TALER_PrivateContractHashP h_control;
    1089             : 
    1090           0 :     switch (TALER_JSON_contract_hash (order,
    1091             :                                       &h_control))
    1092             :     {
    1093           0 :     case GNUNET_SYSERR:
    1094           0 :       GNUNET_break (0);
    1095           0 :       return TALER_MHD_reply_with_error (
    1096             :         connection,
    1097             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
    1098             :         TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    1099             :         "could not compute hash of patched order");
    1100           0 :     case GNUNET_NO:
    1101           0 :       GNUNET_break_op (0);
    1102           0 :       return TALER_MHD_reply_with_error (
    1103             :         connection,
    1104             :         MHD_HTTP_BAD_REQUEST,
    1105             :         TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
    1106             :         "order contained unallowed values");
    1107           0 :     case GNUNET_OK:
    1108           0 :       break;
    1109             :     }
    1110           0 :   }
    1111           0 :   return execute_order (connection,
    1112             :                         hc,
    1113             :                         h_post_data,
    1114             :                         order,
    1115             :                         claim_token,
    1116             :                         inventory_products_length,
    1117             :                         inventory_products,
    1118             :                         uuids_length,
    1119             :                         uuids);
    1120             : }
    1121             : 
    1122             : 
    1123             : /**
    1124             :  * Process the @a payment_target and add the details of how the
    1125             :  * order could be paid to @a order. On success, continue
    1126             :  * processing with patch_order().
    1127             :  *
    1128             :  * @param connection connection to write the result or error to
    1129             :  * @param hc handler context for the request
    1130             :  * @param h_post_data hash of the client's POST request, for idempotency checks
    1131             :  * @param[in,out] order order to process (can be modified)
    1132             :  * @param claim_token token to use for access control
    1133             :  * @param refund_delay refund delay
    1134             :  * @param payment_target desired wire method, NULL for no preference
    1135             :  * @param inventory_products_length length of the @a inventory_products array
    1136             :  * @param inventory_products array of products to add to @a order from our inventory
    1137             :  * @param uuids_length length of the @a uuids array
    1138             :  * @param uuids array of UUIDs used to reserve products from @a inventory_products
    1139             :  * @return MHD result code
    1140             :  */
    1141             : static MHD_RESULT
    1142           0 : add_payment_details (struct MHD_Connection *connection,
    1143             :                      struct TMH_HandlerContext *hc,
    1144             :                      const struct TALER_MerchantPostDataHashP *h_post_data,
    1145             :                      json_t *order,
    1146             :                      const struct TALER_ClaimTokenP *claim_token,
    1147             :                      struct GNUNET_TIME_Relative refund_delay,
    1148             :                      const char *payment_target,
    1149             :                      unsigned int inventory_products_length,
    1150             :                      const struct InventoryProduct inventory_products[],
    1151             :                      unsigned int uuids_length,
    1152             :                      const struct GNUNET_Uuid uuids[])
    1153             : {
    1154             :   struct TMH_WireMethod *wm;
    1155             : 
    1156           0 :   wm = hc->instance->wm_head;
    1157             :   /* Locate wire method that has a matching payment target */
    1158           0 :   while ( (NULL != wm) &&
    1159           0 :           ( (! wm->active) ||
    1160           0 :             ( (NULL != payment_target) &&
    1161           0 :               (0 != strcasecmp (payment_target,
    1162           0 :                                 wm->wire_method) ) ) ) )
    1163           0 :     wm = wm->next;
    1164           0 :   if (NULL == wm)
    1165             :   {
    1166           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1167             :                 "No wire method available for instance '%s'\n",
    1168             :                 hc->instance->settings.id);
    1169           0 :     return TALER_MHD_reply_with_error (connection,
    1170             :                                        MHD_HTTP_NOT_FOUND,
    1171             :                                        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
    1172             :                                        payment_target);
    1173             :   }
    1174           0 :   GNUNET_assert (0 ==
    1175             :                  json_object_set_new (order,
    1176             :                                       "h_wire",
    1177             :                                       GNUNET_JSON_from_data_auto (
    1178             :                                         &wm->h_wire)));
    1179           0 :   GNUNET_assert (0 ==
    1180             :                  json_object_set_new (order,
    1181             :                                       "wire_method",
    1182             :                                       json_string (wm->wire_method)));
    1183           0 :   return patch_order (connection,
    1184             :                       hc,
    1185             :                       h_post_data,
    1186             :                       order,
    1187             :                       claim_token,
    1188             :                       refund_delay,
    1189             :                       inventory_products_length,
    1190             :                       inventory_products,
    1191             :                       uuids_length,
    1192             :                       uuids);
    1193             : }
    1194             : 
    1195             : 
    1196             : /**
    1197             :  * Merge the inventory products into @a order, querying the
    1198             :  * database about the details of those products. Upon success,
    1199             :  * continue processing by calling add_payment_details().
    1200             :  *
    1201             :  * @param connection connection to write the result or error to
    1202             :  * @param hc handler context for the request
    1203             :  * @param h_post_data hash of the client's POST request, for idempotency checks
    1204             :  * @param[in,out] order order to process (can be modified)
    1205             :  * @param claim_token token to use for access control
    1206             :  * @param refund_delay time window where it is possible to ask a refund
    1207             :  * @param payment_target RFC8905 payment target type to find a matching merchant account
    1208             :  * @param inventory_products_length length of the @a inventory_products array
    1209             :  * @param inventory_products array of products to add to @a order from our inventory
    1210             :  * @param uuids_length length of the @a uuids array
    1211             :  * @param uuids array of UUIDs used to reserve products from @a inventory_products
    1212             :  * @return MHD result code
    1213             :  */
    1214             : static MHD_RESULT
    1215           0 : merge_inventory (struct MHD_Connection *connection,
    1216             :                  struct TMH_HandlerContext *hc,
    1217             :                  const struct TALER_MerchantPostDataHashP *h_post_data,
    1218             :                  json_t *order,
    1219             :                  const struct TALER_ClaimTokenP *claim_token,
    1220             :                  struct GNUNET_TIME_Relative refund_delay,
    1221             :                  const char *payment_target,
    1222             :                  unsigned int inventory_products_length,
    1223             :                  const struct InventoryProduct inventory_products[],
    1224             :                  unsigned int uuids_length,
    1225             :                  const struct GNUNET_Uuid uuids[])
    1226             : {
    1227             :   /**
    1228             :    * inventory_products => instructions to add products to contract terms
    1229             :    * order.products => contains products that are not from the backend-managed inventory.
    1230             :    */
    1231           0 :   GNUNET_assert (NULL != order);
    1232             :   {
    1233           0 :     json_t *jprod = json_object_get (order,
    1234             :                                      "products");
    1235           0 :     if (NULL == jprod)
    1236             :     {
    1237           0 :       GNUNET_assert (0 ==
    1238             :                      json_object_set_new (order,
    1239             :                                           "products",
    1240             :                                           json_array ()));
    1241             :     }
    1242           0 :     else if (! TMH_products_array_valid (jprod))
    1243             :     {
    1244           0 :       return TALER_MHD_reply_with_error (connection,
    1245             :                                          MHD_HTTP_BAD_REQUEST,
    1246             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1247             :                                          "order.products");
    1248             :     }
    1249             :   }
    1250             : 
    1251             :   /* Populate products from inventory product array and database */
    1252             :   {
    1253           0 :     json_t *np = json_array ();
    1254             : 
    1255           0 :     for (unsigned int i = 0; i<inventory_products_length; i++)
    1256             :     {
    1257             :       struct TALER_MERCHANTDB_ProductDetails pd;
    1258             :       enum GNUNET_DB_QueryStatus qs;
    1259             : 
    1260           0 :       qs = TMH_db->lookup_product (TMH_db->cls,
    1261           0 :                                    hc->instance->settings.id,
    1262           0 :                                    inventory_products[i].product_id,
    1263             :                                    &pd);
    1264           0 :       if (qs <= 0)
    1265             :       {
    1266           0 :         enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    1267           0 :         unsigned int http_status = 0;
    1268             : 
    1269           0 :         switch (qs)
    1270             :         {
    1271           0 :         case GNUNET_DB_STATUS_HARD_ERROR:
    1272           0 :           GNUNET_break (0);
    1273           0 :           http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    1274           0 :           ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
    1275           0 :           break;
    1276           0 :         case GNUNET_DB_STATUS_SOFT_ERROR:
    1277           0 :           GNUNET_break (0);
    1278           0 :           http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    1279           0 :           ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
    1280           0 :           break;
    1281           0 :         case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    1282           0 :           http_status = MHD_HTTP_NOT_FOUND;
    1283           0 :           ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
    1284           0 :           break;
    1285           0 :         case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    1286             :           /* case listed to make compilers happy */
    1287           0 :           GNUNET_assert (0);
    1288             :         }
    1289           0 :         json_decref (np);
    1290           0 :         return TALER_MHD_reply_with_error (connection,
    1291             :                                            http_status,
    1292             :                                            ec,
    1293           0 :                                            inventory_products[i].product_id);
    1294             :       }
    1295             :       {
    1296             :         json_t *p;
    1297             : 
    1298           0 :         p = GNUNET_JSON_PACK (
    1299             :           GNUNET_JSON_pack_string ("description",
    1300             :                                    pd.description),
    1301             :           GNUNET_JSON_pack_object_steal ("description_i18n",
    1302             :                                          pd.description_i18n),
    1303             :           GNUNET_JSON_pack_string ("unit",
    1304             :                                    pd.unit),
    1305             :           TALER_JSON_pack_amount ("price",
    1306             :                                   &pd.price),
    1307             :           GNUNET_JSON_pack_array_steal ("taxes",
    1308             :                                         pd.taxes),
    1309             :           GNUNET_JSON_pack_string ("image",
    1310             :                                    pd.image),
    1311             :           GNUNET_JSON_pack_uint64 ("quantity",
    1312             :                                    inventory_products[i].
    1313             :                                    quantity));
    1314           0 :         GNUNET_assert (NULL != p);
    1315           0 :         GNUNET_assert (0 ==
    1316             :                        json_array_append_new (np,
    1317             :                                               p));
    1318             :       }
    1319           0 :       GNUNET_free (pd.description);
    1320           0 :       GNUNET_free (pd.unit);
    1321           0 :       GNUNET_free (pd.image);
    1322           0 :       json_decref (pd.address);
    1323             :     }
    1324             :     /* merge into existing products list */
    1325             :     {
    1326             :       json_t *xp;
    1327             : 
    1328           0 :       xp = json_object_get (order,
    1329             :                             "products");
    1330           0 :       GNUNET_assert (NULL != xp);
    1331           0 :       json_array_extend (xp, np);
    1332           0 :       json_decref (np);
    1333             :     }
    1334             :   }
    1335           0 :   return add_payment_details (connection,
    1336             :                               hc,
    1337             :                               h_post_data,
    1338             :                               order,
    1339             :                               claim_token,
    1340             :                               refund_delay,
    1341             :                               payment_target,
    1342             :                               inventory_products_length,
    1343             :                               inventory_products,
    1344             :                               uuids_length,
    1345             :                               uuids);
    1346             : }
    1347             : 
    1348             : 
    1349             : /**
    1350             :  * Generate an order.  We add the fields 'exchanges', 'merchant_pub', and
    1351             :  * 'H_wire' to the order gotten from the frontend, as well as possibly other
    1352             :  * fields if the frontend did not provide them. Returns the order_id.
    1353             :  *
    1354             :  * @param rh context of the handler
    1355             :  * @param connection the MHD connection to handle
    1356             :  * @param[in,out] hc context with further information about the request
    1357             :  * @return MHD result code
    1358             :  */
    1359             : MHD_RESULT
    1360           0 : TMH_private_post_orders (const struct TMH_RequestHandler *rh,
    1361             :                          struct MHD_Connection *connection,
    1362             :                          struct TMH_HandlerContext *hc)
    1363             : {
    1364             :   json_t *order;
    1365           0 :   struct GNUNET_TIME_Relative refund_delay = GNUNET_TIME_UNIT_ZERO;
    1366           0 :   const char *payment_target = NULL;
    1367           0 :   json_t *ip = NULL;
    1368           0 :   unsigned int ips_len = 0;
    1369           0 :   struct InventoryProduct *ips = NULL;
    1370           0 :   unsigned int uuids_len = 0;
    1371             :   json_t *uuid;
    1372           0 :   struct GNUNET_Uuid *uuids = NULL;
    1373             :   struct TALER_ClaimTokenP claim_token;
    1374           0 :   bool create_token = true; /* default */
    1375             :   struct GNUNET_JSON_Specification spec[] = {
    1376           0 :     GNUNET_JSON_spec_json ("order",
    1377             :                            &order),
    1378           0 :     GNUNET_JSON_spec_mark_optional (
    1379             :       GNUNET_JSON_spec_relative_time ("refund_delay",
    1380             :                                       &refund_delay),
    1381             :       NULL),
    1382           0 :     GNUNET_JSON_spec_mark_optional (
    1383             :       GNUNET_JSON_spec_string ("payment_target",
    1384             :                                &payment_target),
    1385             :       NULL),
    1386           0 :     GNUNET_JSON_spec_mark_optional (
    1387             :       GNUNET_JSON_spec_json ("inventory_products",
    1388             :                              &ip),
    1389             :       NULL),
    1390           0 :     GNUNET_JSON_spec_mark_optional (
    1391             :       GNUNET_JSON_spec_json ("lock_uuids",
    1392             :                              &uuid),
    1393             :       NULL),
    1394           0 :     GNUNET_JSON_spec_mark_optional (
    1395             :       GNUNET_JSON_spec_bool ("create_token",
    1396             :                              &create_token),
    1397             :       NULL),
    1398           0 :     GNUNET_JSON_spec_end ()
    1399             :   };
    1400             :   enum GNUNET_GenericReturnValue ret;
    1401             :   struct TALER_MerchantPostDataHashP h_post_data;
    1402             : 
    1403             :   (void) rh;
    1404           0 :   ret = TALER_MHD_parse_json_data (connection,
    1405           0 :                                    hc->request_body,
    1406             :                                    spec);
    1407           0 :   if (GNUNET_OK != ret)
    1408             :     return (GNUNET_NO == ret)
    1409             :            ? MHD_YES
    1410           0 :            : MHD_NO;
    1411             : 
    1412           0 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    1413             :               "Refund delay is %s\n",
    1414             :               GNUNET_TIME_relative2s (refund_delay,
    1415             :                                       false));
    1416             : 
    1417           0 :   TMH_db->expire_locks (TMH_db->cls);
    1418           0 :   if (create_token)
    1419             :   {
    1420           0 :     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    1421             :                                 &claim_token,
    1422             :                                 sizeof (claim_token));
    1423             :   }
    1424             :   else
    1425             :   {
    1426             :     /* we use all-zeros for 'no token' */
    1427           0 :     memset (&claim_token,
    1428             :             0,
    1429             :             sizeof (claim_token));
    1430             :   }
    1431             : 
    1432             :   /* Compute h_post_data (for idempotency check) */
    1433             :   {
    1434             :     char *req_body_enc;
    1435             : 
    1436             :     /* Dump normalized JSON to string. */
    1437           0 :     if (NULL == (req_body_enc = json_dumps (hc->request_body,
    1438             :                                             JSON_ENCODE_ANY
    1439             :                                             | JSON_COMPACT
    1440             :                                             | JSON_SORT_KEYS)))
    1441             :     {
    1442           0 :       GNUNET_break (0);
    1443           0 :       GNUNET_JSON_parse_free (spec);
    1444           0 :       return TALER_MHD_reply_with_error (connection,
    1445             :                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    1446             :                                          TALER_EC_GENERIC_ALLOCATION_FAILURE,
    1447             :                                          "request body normalization for hashing");
    1448             :     }
    1449           0 :     GNUNET_CRYPTO_hash (req_body_enc,
    1450             :                         strlen (req_body_enc),
    1451             :                         &h_post_data.hash);
    1452           0 :     GNUNET_free (req_body_enc);
    1453             :   }
    1454             : 
    1455             :   /* parse the inventory_products (optionally given) */
    1456           0 :   if (NULL != ip)
    1457             :   {
    1458           0 :     if (! json_is_array (ip))
    1459             :     {
    1460           0 :       GNUNET_JSON_parse_free (spec);
    1461           0 :       return TALER_MHD_reply_with_error (connection,
    1462             :                                          MHD_HTTP_BAD_REQUEST,
    1463             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1464             :                                          "inventory_products");
    1465             :     }
    1466           0 :     GNUNET_array_grow (ips,
    1467             :                        ips_len,
    1468             :                        json_array_size (ip));
    1469           0 :     for (unsigned int i = 0; i<ips_len; i++)
    1470             :     {
    1471             :       const char *error_name;
    1472             :       unsigned int error_line;
    1473             :       struct GNUNET_JSON_Specification ispec[] = {
    1474           0 :         GNUNET_JSON_spec_string ("product_id",
    1475           0 :                                  &ips[i].product_id),
    1476           0 :         GNUNET_JSON_spec_uint32 ("quantity",
    1477           0 :                                  &ips[i].quantity),
    1478           0 :         GNUNET_JSON_spec_end ()
    1479             :       };
    1480             : 
    1481           0 :       ret = GNUNET_JSON_parse (json_array_get (ip,
    1482             :                                                i),
    1483             :                                ispec,
    1484             :                                &error_name,
    1485             :                                &error_line);
    1486           0 :       if (GNUNET_OK != ret)
    1487             :       {
    1488           0 :         GNUNET_break_op (0);
    1489           0 :         GNUNET_array_grow (ips,
    1490             :                            ips_len,
    1491             :                            0);
    1492           0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1493             :                     "Product parsing failed at #%u: %s:%u\n",
    1494             :                     i,
    1495             :                     error_name,
    1496             :                     error_line);
    1497           0 :         GNUNET_JSON_parse_free (spec);
    1498           0 :         return TALER_MHD_reply_with_error (connection,
    1499             :                                            MHD_HTTP_BAD_REQUEST,
    1500             :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1501             :                                            "inventory_products");
    1502             :       }
    1503             :     }
    1504             :   }
    1505             : 
    1506             :   /* parse the lock_uuids (optionally given) */
    1507           0 :   if (NULL != uuid)
    1508             :   {
    1509           0 :     if (! json_is_array (uuid))
    1510             :     {
    1511           0 :       GNUNET_array_grow (ips,
    1512             :                          ips_len,
    1513             :                          0);
    1514           0 :       GNUNET_JSON_parse_free (spec);
    1515           0 :       return TALER_MHD_reply_with_error (connection,
    1516             :                                          MHD_HTTP_BAD_REQUEST,
    1517             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1518             :                                          "lock_uuids");
    1519             :     }
    1520           0 :     GNUNET_array_grow (uuids,
    1521             :                        uuids_len,
    1522             :                        json_array_size (uuid));
    1523           0 :     for (unsigned int i = 0; i<uuids_len; i++)
    1524             :     {
    1525           0 :       json_t *ui = json_array_get (uuid,
    1526             :                                    i);
    1527             : 
    1528           0 :       if (! json_is_string (ui))
    1529             :       {
    1530           0 :         GNUNET_break_op (0);
    1531           0 :         GNUNET_array_grow (ips,
    1532             :                            ips_len,
    1533             :                            0);
    1534           0 :         GNUNET_array_grow (uuids,
    1535             :                            uuids_len,
    1536             :                            0);
    1537           0 :         GNUNET_JSON_parse_free (spec);
    1538           0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1539             :                     "UUID parsing failed at #%u\n",
    1540             :                     i);
    1541           0 :         return TALER_MHD_reply_with_error (connection,
    1542             :                                            MHD_HTTP_BAD_REQUEST,
    1543             :                                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1544             :                                            "lock_uuids");
    1545             :       }
    1546           0 :       TMH_uuid_from_string (json_string_value (ui),
    1547           0 :                             &uuids[i]);
    1548             :     }
    1549             :   }
    1550             : 
    1551             :   /* Finally, start by completing the order */
    1552             :   {
    1553             :     MHD_RESULT res;
    1554             : 
    1555           0 :     res = merge_inventory (connection,
    1556             :                            hc,
    1557             :                            &h_post_data,
    1558             :                            order,
    1559             :                            &claim_token,
    1560             :                            refund_delay,
    1561             :                            payment_target,
    1562             :                            ips_len,
    1563             :                            ips,
    1564             :                            uuids_len,
    1565             :                            uuids);
    1566           0 :     GNUNET_array_grow (ips,
    1567             :                        ips_len,
    1568             :                        0);
    1569           0 :     GNUNET_array_grow (uuids,
    1570             :                        uuids_len,
    1571             :                        0);
    1572           0 :     GNUNET_JSON_parse_free (spec);
    1573           0 :     return res;
    1574             :   }
    1575             : }
    1576             : 
    1577             : 
    1578             : /* end of taler-merchant-httpd_private-post-orders.c */

Generated by: LCOV version 1.14