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

Generated by: LCOV version 1.14