merchant

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

merchant_api_post-private-orders.c (17023B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Lesser General Public License as published by the Free Software
      7   Foundation; either version 2.1, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
     12 
     13   You should have received a copy of the GNU Lesser General Public License along with
     14   TALER; see the file COPYING.LGPL.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file src/lib/merchant_api_post-private-orders.c
     19  * @brief Implementation of the POST /private/orders request
     20  * @author Christian Grothoff
     21  */
     22 #include "platform.h"
     23 #include <curl/curl.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include <taler/merchant/post-private-orders.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include "merchant_api_common.h"
     31 #include <taler/taler_json_lib.h>
     32 #include <taler/taler_curl_lib.h>
     33 
     34 
     35 /**
     36  * Maximum number of exchange rejections we allow in a response before
     37  * we consider the (untrusted) reply malformed.  Bounds the stack VLA
     38  * used to parse the array.
     39  */
     40 #define MAX_EXCHANGE_REJECTIONS 1024
     41 
     42 
     43 /**
     44  * Handle for a POST /private/orders operation.
     45  */
     46 struct TALER_MERCHANT_PostPrivateOrdersHandle
     47 {
     48   /**
     49    * Base URL of the merchant backend.
     50    */
     51   char *base_url;
     52 
     53   /**
     54    * The full URL for this request.
     55    */
     56   char *url;
     57 
     58   /**
     59    * Handle for the request.
     60    */
     61   struct GNUNET_CURL_Job *job;
     62 
     63   /**
     64    * Function to call with the result.
     65    */
     66   TALER_MERCHANT_PostPrivateOrdersCallback cb;
     67 
     68   /**
     69    * Closure for @a cb.
     70    */
     71   TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls;
     72 
     73   /**
     74    * Reference to the execution context.
     75    */
     76   struct GNUNET_CURL_Context *ctx;
     77 
     78   /**
     79    * Minor context that holds body and headers.
     80    */
     81   struct TALER_CURL_PostContext post_ctx;
     82 
     83   /**
     84    * Order contract terms (JSON).
     85    */
     86   json_t *order;
     87 
     88   /**
     89    * Optional refund delay.
     90    */
     91   struct GNUNET_TIME_Relative refund_delay;
     92 
     93   /**
     94    * Whether refund_delay was set.
     95    */
     96   bool refund_delay_set;
     97 
     98   /**
     99    * Optional payment target.
    100    */
    101   const char *payment_target;
    102 
    103   /**
    104    * Optional session ID.
    105    */
    106   const char *session_id;
    107 
    108   /**
    109    * Whether to create a claim token (default: true).
    110    */
    111   bool create_token;
    112 
    113   /**
    114    * Optional OTP device ID.
    115    */
    116   const char *otp_id;
    117 
    118   /**
    119    * Optional inventory products.
    120    */
    121   const struct TALER_MERCHANT_PostPrivateOrdersInventoryProduct *
    122     inventory_products;
    123 
    124   /**
    125    * Number of inventory products.
    126    */
    127   unsigned int num_inventory_products;
    128 
    129   /**
    130    * Optional lock UUIDs.
    131    */
    132   const char **lock_uuids;
    133 
    134   /**
    135    * Number of lock UUIDs.
    136    */
    137   unsigned int num_lock_uuids;
    138 };
    139 
    140 
    141 /**
    142  * Function called when we're done processing the
    143  * HTTP POST /private/orders request.
    144  *
    145  * @param cls the `struct TALER_MERCHANT_PostPrivateOrdersHandle`
    146  * @param response_code HTTP response code, 0 on error
    147  * @param response response body, NULL if not in JSON
    148  */
    149 static void
    150 handle_post_orders_finished (void *cls,
    151                              long response_code,
    152                              const void *response)
    153 {
    154   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh = cls;
    155   const json_t *json = response;
    156   struct TALER_MERCHANT_PostPrivateOrdersResponse por = {
    157     .hr.http_status = (unsigned int) response_code,
    158     .hr.reply = json
    159   };
    160   struct TALER_ClaimTokenP token;
    161 
    162   ppoh->job = NULL;
    163   switch (response_code)
    164   {
    165   case 0:
    166     por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    167     break;
    168   case MHD_HTTP_OK:
    169     {
    170       bool no_token;
    171       bool no_pay_deadline;
    172       struct GNUNET_JSON_Specification spec[] = {
    173         GNUNET_JSON_spec_string ("order_id",
    174                                  &por.details.ok.order_id),
    175         GNUNET_JSON_spec_mark_optional (
    176           GNUNET_JSON_spec_fixed_auto ("token",
    177                                        &token),
    178           &no_token),
    179         GNUNET_JSON_spec_mark_optional (
    180           GNUNET_JSON_spec_timestamp ("pay_deadline",
    181                                       &por.details.ok.pay_deadline),
    182           &no_pay_deadline),
    183         GNUNET_JSON_spec_end ()
    184       };
    185 
    186       if (GNUNET_OK !=
    187           GNUNET_JSON_parse (json,
    188                              spec,
    189                              NULL, NULL))
    190       {
    191         GNUNET_break_op (0);
    192         por.hr.http_status = 0;
    193         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    194         break;
    195       }
    196       if (! no_token)
    197         por.details.ok.token = &token;
    198       if (no_pay_deadline)
    199         por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
    200       break;
    201     }
    202   case MHD_HTTP_BAD_REQUEST:
    203     por.hr.ec = TALER_JSON_get_error_code (json);
    204     por.hr.hint = TALER_JSON_get_error_hint (json);
    205     break;
    206   case MHD_HTTP_UNAUTHORIZED:
    207     por.hr.ec = TALER_JSON_get_error_code (json);
    208     por.hr.hint = TALER_JSON_get_error_hint (json);
    209     break;
    210   case MHD_HTTP_FORBIDDEN:
    211     por.hr.ec = TALER_JSON_get_error_code (json);
    212     por.hr.hint = TALER_JSON_get_error_hint (json);
    213     break;
    214   case MHD_HTTP_NOT_FOUND:
    215     por.hr.ec = TALER_JSON_get_error_code (json);
    216     por.hr.hint = TALER_JSON_get_error_hint (json);
    217     break;
    218   case MHD_HTTP_CONFLICT:
    219     por.hr.ec = TALER_JSON_get_error_code (json);
    220     por.hr.hint = TALER_JSON_get_error_hint (json);
    221     break;
    222   case MHD_HTTP_GONE:
    223     {
    224       bool rq_frac_missing;
    225       bool aq_frac_missing;
    226       struct GNUNET_JSON_Specification spec[] = {
    227         GNUNET_JSON_spec_string (
    228           "product_id",
    229           &por.details.gone.product_id),
    230         GNUNET_JSON_spec_uint64 (
    231           "requested_quantity",
    232           &por.details.gone.requested_quantity),
    233         GNUNET_JSON_spec_mark_optional (
    234           GNUNET_JSON_spec_uint32 (
    235             "requested_quantity_frac",
    236             &por.details.gone.requested_quantity_frac),
    237           &rq_frac_missing),
    238         GNUNET_JSON_spec_uint64 (
    239           "available_quantity",
    240           &por.details.gone.available_quantity),
    241         GNUNET_JSON_spec_mark_optional (
    242           GNUNET_JSON_spec_uint32 (
    243             "available_quantity_frac",
    244             &por.details.gone.available_quantity_frac),
    245           &aq_frac_missing),
    246         GNUNET_JSON_spec_mark_optional (
    247           GNUNET_JSON_spec_string (
    248             "unit_requested_quantity",
    249             &por.details.gone.unit_requested_quantity),
    250           NULL),
    251         GNUNET_JSON_spec_mark_optional (
    252           GNUNET_JSON_spec_string (
    253             "unit_available_quantity",
    254             &por.details.gone.unit_available_quantity),
    255           NULL),
    256         GNUNET_JSON_spec_mark_optional (
    257           GNUNET_JSON_spec_timestamp (
    258             "restock_expected",
    259             &por.details.gone.restock_expected),
    260           NULL),
    261         GNUNET_JSON_spec_end ()
    262       };
    263 
    264       if (GNUNET_OK !=
    265           GNUNET_JSON_parse (json,
    266                              spec,
    267                              NULL, NULL))
    268       {
    269         GNUNET_break_op (0);
    270         por.hr.http_status = 0;
    271         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    272       }
    273       else
    274       {
    275         if (rq_frac_missing)
    276           por.details.gone.requested_quantity_frac = 0;
    277         if (aq_frac_missing)
    278           por.details.gone.available_quantity_frac = 0;
    279       }
    280       break;
    281     }
    282   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    283     {
    284       const json_t *jer = NULL;
    285 
    286       por.hr.ec = TALER_JSON_get_error_code (json);
    287       por.hr.hint = TALER_JSON_get_error_hint (json);
    288       jer = json_object_get (json,
    289                              "exchange_rejections");
    290       if ( (NULL != jer) &&
    291            json_is_array (jer) )
    292       {
    293         unsigned int rej_len = (unsigned int) json_array_size (jer);
    294 
    295         if ( (json_array_size (jer) == (size_t) rej_len) &&
    296              (rej_len <= MAX_EXCHANGE_REJECTIONS) )
    297         {
    298           struct TALER_MERCHANT_ExchangeRejectionDetail rejs[
    299             GNUNET_NZL (rej_len)];
    300           bool ok = true;
    301 
    302           memset (rejs, 0, sizeof (rejs));
    303           for (unsigned int i = 0; i < rej_len; i++)
    304           {
    305             struct GNUNET_JSON_Specification rspec[] = {
    306               TALER_JSON_spec_web_url (
    307                 "exchange_url",
    308                 &rejs[i].exchange_url),
    309               TALER_JSON_spec_ec (
    310                 "code",
    311                 &rejs[i].code),
    312               GNUNET_JSON_spec_mark_optional (
    313                 GNUNET_JSON_spec_string (
    314                   "hint",
    315                   &rejs[i].hint),
    316                 NULL),
    317               GNUNET_JSON_spec_end ()
    318             };
    319 
    320             if (GNUNET_OK !=
    321                 GNUNET_JSON_parse (json_array_get (jer, i),
    322                                    rspec,
    323                                    NULL, NULL))
    324             {
    325               GNUNET_break_op (0);
    326               ok = false;
    327               break;
    328             }
    329           }
    330           if (ok)
    331           {
    332             por.details.unavailable_for_legal_reasons
    333             .num_exchange_rejections = rej_len;
    334             por.details.unavailable_for_legal_reasons
    335             .exchange_rejections = rejs;
    336             ppoh->cb (ppoh->cb_cls,
    337                       &por);
    338             TALER_MERCHANT_post_private_orders_cancel (ppoh);
    339             return;
    340           }
    341         }
    342       }
    343       break;
    344     }
    345   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    346     por.hr.ec = TALER_JSON_get_error_code (json);
    347     por.hr.hint = TALER_JSON_get_error_hint (json);
    348     break;
    349   default:
    350     por.hr.ec = TALER_JSON_get_error_code (json);
    351     por.hr.hint = TALER_JSON_get_error_hint (json);
    352     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    353                 "Unexpected response code %u/%d\n",
    354                 (unsigned int) response_code,
    355                 (int) por.hr.ec);
    356     GNUNET_break_op (0);
    357     break;
    358   }
    359   ppoh->cb (ppoh->cb_cls,
    360             &por);
    361   TALER_MERCHANT_post_private_orders_cancel (ppoh);
    362 }
    363 
    364 
    365 struct TALER_MERCHANT_PostPrivateOrdersHandle *
    366 TALER_MERCHANT_post_private_orders_create (
    367   struct GNUNET_CURL_Context *ctx,
    368   const char *url,
    369   const json_t *order)
    370 {
    371   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh;
    372 
    373   ppoh = GNUNET_new (struct TALER_MERCHANT_PostPrivateOrdersHandle);
    374   ppoh->ctx = ctx;
    375   ppoh->base_url = GNUNET_strdup (url);
    376   ppoh->order = json_incref ((json_t *) order);
    377   ppoh->create_token = true;
    378   return ppoh;
    379 }
    380 
    381 
    382 enum GNUNET_GenericReturnValue
    383 TALER_MERCHANT_post_private_orders_set_options_ (
    384   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh,
    385   unsigned int num_options,
    386   const struct TALER_MERCHANT_PostPrivateOrdersOptionValue *options)
    387 {
    388   for (unsigned int i = 0; i < num_options; i++)
    389   {
    390     switch (options[i].option)
    391     {
    392     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_END:
    393       return GNUNET_OK;
    394     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_REFUND_DELAY:
    395       ppoh->refund_delay = options[i].details.refund_delay;
    396       ppoh->refund_delay_set = true;
    397       break;
    398     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_PAYMENT_TARGET:
    399       ppoh->payment_target = options[i].details.payment_target;
    400       break;
    401     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_SESSION_ID:
    402       ppoh->session_id = options[i].details.session_id;
    403       break;
    404     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_CREATE_TOKEN:
    405       ppoh->create_token = options[i].details.create_token;
    406       break;
    407     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_OTP_ID:
    408       ppoh->otp_id = options[i].details.otp_id;
    409       break;
    410     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_INVENTORY_PRODUCTS:
    411       ppoh->num_inventory_products
    412         = options[i].details.inventory_products.num;
    413       ppoh->inventory_products
    414         = options[i].details.inventory_products.products;
    415       break;
    416     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_LOCK_UUIDS:
    417       ppoh->num_lock_uuids = options[i].details.lock_uuids.num;
    418       ppoh->lock_uuids = options[i].details.lock_uuids.uuids;
    419       break;
    420     default:
    421       GNUNET_break (0);
    422       return GNUNET_SYSERR;
    423     }
    424   }
    425   return GNUNET_OK;
    426 }
    427 
    428 
    429 enum TALER_ErrorCode
    430 TALER_MERCHANT_post_private_orders_start (
    431   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh,
    432   TALER_MERCHANT_PostPrivateOrdersCallback cb,
    433   TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls)
    434 {
    435   json_t *req;
    436   CURL *eh;
    437 
    438   ppoh->cb = cb;
    439   ppoh->cb_cls = cb_cls;
    440   ppoh->url = TALER_url_join (ppoh->base_url,
    441                               "private/orders",
    442                               NULL);
    443   if (NULL == ppoh->url)
    444     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    445   req = GNUNET_JSON_PACK (
    446     GNUNET_JSON_pack_object_incref ("order",
    447                                     ppoh->order),
    448     GNUNET_JSON_pack_allow_null (
    449       GNUNET_JSON_pack_string ("session_id",
    450                                ppoh->session_id)),
    451     GNUNET_JSON_pack_allow_null (
    452       GNUNET_JSON_pack_string ("payment_target",
    453                                ppoh->payment_target)),
    454     GNUNET_JSON_pack_allow_null (
    455       GNUNET_JSON_pack_string ("otp_id",
    456                                ppoh->otp_id)));
    457   if (ppoh->refund_delay_set &&
    458       (0 != ppoh->refund_delay.rel_value_us))
    459   {
    460     GNUNET_assert (0 ==
    461                    json_object_set_new (req,
    462                                         "refund_delay",
    463                                         GNUNET_JSON_from_time_rel (
    464                                           ppoh->refund_delay)));
    465   }
    466   if (0 != ppoh->num_inventory_products)
    467   {
    468     json_t *ipa = json_array ();
    469 
    470     GNUNET_assert (NULL != ipa);
    471     for (unsigned int i = 0; i < ppoh->num_inventory_products; i++)
    472     {
    473       json_t *ip;
    474 
    475       {
    476         char unit_quantity_buf[64];
    477 
    478         TALER_MERCHANT_format_quantity_string (
    479           ppoh->inventory_products[i].quantity,
    480           ppoh->inventory_products[i].use_fractional_quantity
    481           ? ppoh->inventory_products[i].quantity_frac
    482           : 0,
    483           unit_quantity_buf,
    484           sizeof (unit_quantity_buf));
    485         ip = GNUNET_JSON_PACK (
    486           GNUNET_JSON_pack_string ("product_id",
    487                                    ppoh->inventory_products[i].product_id),
    488           GNUNET_JSON_pack_string ("unit_quantity",
    489                                    unit_quantity_buf));
    490       }
    491       if (ppoh->inventory_products[i].product_money_pot > 0)
    492       {
    493         GNUNET_assert (
    494           0 ==
    495           json_object_set_new (
    496             ip,
    497             "product_money_pot",
    498             json_integer (
    499               ppoh->inventory_products[i].product_money_pot)));
    500       }
    501       GNUNET_assert (0 ==
    502                      json_array_append_new (ipa,
    503                                             ip));
    504     }
    505     GNUNET_assert (0 ==
    506                    json_object_set_new (req,
    507                                         "inventory_products",
    508                                         ipa));
    509   }
    510   if (0 != ppoh->num_lock_uuids)
    511   {
    512     json_t *ua = json_array ();
    513 
    514     GNUNET_assert (NULL != ua);
    515     for (unsigned int i = 0; i < ppoh->num_lock_uuids; i++)
    516     {
    517       GNUNET_assert (0 ==
    518                      json_array_append_new (ua,
    519                                             json_string (
    520                                               ppoh->lock_uuids[i])));
    521     }
    522     GNUNET_assert (0 ==
    523                    json_object_set_new (req,
    524                                         "lock_uuids",
    525                                         ua));
    526   }
    527   if (! ppoh->create_token)
    528   {
    529     GNUNET_assert (0 ==
    530                    json_object_set_new (req,
    531                                         "create_token",
    532                                         json_boolean (ppoh->create_token)));
    533   }
    534   eh = TALER_MERCHANT_curl_easy_get_ (ppoh->url);
    535   if ( (NULL == eh) ||
    536        (GNUNET_OK !=
    537         TALER_curl_easy_post (&ppoh->post_ctx,
    538                               eh,
    539                               req)) )
    540   {
    541     GNUNET_break (0);
    542     json_decref (req);
    543     if (NULL != eh)
    544       curl_easy_cleanup (eh);
    545     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    546   }
    547   json_decref (req);
    548   ppoh->job = GNUNET_CURL_job_add2 (ppoh->ctx,
    549                                     eh,
    550                                     ppoh->post_ctx.headers,
    551                                     &handle_post_orders_finished,
    552                                     ppoh);
    553   if (NULL == ppoh->job)
    554     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    555   return TALER_EC_NONE;
    556 }
    557 
    558 
    559 void
    560 TALER_MERCHANT_post_private_orders_cancel (
    561   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh)
    562 {
    563   if (NULL != ppoh->job)
    564   {
    565     GNUNET_CURL_job_cancel (ppoh->job);
    566     ppoh->job = NULL;
    567   }
    568   TALER_curl_easy_post_finished (&ppoh->post_ctx);
    569   json_decref (ppoh->order);
    570   GNUNET_free (ppoh->url);
    571   GNUNET_free (ppoh->base_url);
    572   GNUNET_free (ppoh);
    573 }
    574 
    575 
    576 /* end of merchant_api_post-private-orders-new.c */