merchant

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

merchant_api_post-private-orders.c (17599B)


      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    * Challenge copied from the options, if present.
    120    */
    121   struct TALER_PosChallengeP challenge;
    122 
    123   bool have_challenge;
    124 
    125   /**
    126    * Optional inventory products.
    127    */
    128   const struct TALER_MERCHANT_PostPrivateOrdersInventoryProduct *
    129     inventory_products;
    130 
    131   /**
    132    * Number of inventory products.
    133    */
    134   unsigned int num_inventory_products;
    135 
    136   /**
    137    * Optional lock UUIDs.
    138    */
    139   const char **lock_uuids;
    140 
    141   /**
    142    * Number of lock UUIDs.
    143    */
    144   unsigned int num_lock_uuids;
    145 };
    146 
    147 
    148 /**
    149  * Function called when we're done processing the
    150  * HTTP POST /private/orders request.
    151  *
    152  * @param cls the `struct TALER_MERCHANT_PostPrivateOrdersHandle`
    153  * @param response_code HTTP response code, 0 on error
    154  * @param response response body, NULL if not in JSON
    155  */
    156 static void
    157 handle_post_orders_finished (void *cls,
    158                              long response_code,
    159                              const void *response)
    160 {
    161   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh = cls;
    162   const json_t *json = response;
    163   struct TALER_MERCHANT_PostPrivateOrdersResponse por = {
    164     .hr.http_status = (unsigned int) response_code,
    165     .hr.reply = json
    166   };
    167   struct TALER_ClaimTokenP token;
    168 
    169   ppoh->job = NULL;
    170   switch (response_code)
    171   {
    172   case 0:
    173     por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    174     break;
    175   case MHD_HTTP_OK:
    176     {
    177       bool no_token;
    178       bool no_pay_deadline;
    179       struct GNUNET_JSON_Specification spec[] = {
    180         TALER_JSON_spec_slug ("order_id",
    181                               &por.details.ok.order_id),
    182         GNUNET_JSON_spec_mark_optional (
    183           GNUNET_JSON_spec_fixed_auto ("token",
    184                                        &token),
    185           &no_token),
    186         GNUNET_JSON_spec_mark_optional (
    187           GNUNET_JSON_spec_timestamp ("pay_deadline",
    188                                       &por.details.ok.pay_deadline),
    189           &no_pay_deadline),
    190         GNUNET_JSON_spec_end ()
    191       };
    192 
    193       if (GNUNET_OK !=
    194           GNUNET_JSON_parse (json,
    195                              spec,
    196                              NULL, NULL))
    197       {
    198         GNUNET_break_op (0);
    199         por.hr.http_status = 0;
    200         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    201         break;
    202       }
    203       if (! no_token)
    204         por.details.ok.token = &token;
    205       if (no_pay_deadline)
    206         por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
    207       break;
    208     }
    209   case MHD_HTTP_BAD_REQUEST:
    210     por.hr.ec = TALER_JSON_get_error_code (json);
    211     por.hr.hint = TALER_JSON_get_error_hint (json);
    212     break;
    213   case MHD_HTTP_UNAUTHORIZED:
    214     por.hr.ec = TALER_JSON_get_error_code (json);
    215     por.hr.hint = TALER_JSON_get_error_hint (json);
    216     break;
    217   case MHD_HTTP_FORBIDDEN:
    218     por.hr.ec = TALER_JSON_get_error_code (json);
    219     por.hr.hint = TALER_JSON_get_error_hint (json);
    220     break;
    221   case MHD_HTTP_NOT_FOUND:
    222     por.hr.ec = TALER_JSON_get_error_code (json);
    223     por.hr.hint = TALER_JSON_get_error_hint (json);
    224     break;
    225   case MHD_HTTP_CONFLICT:
    226     por.hr.ec = TALER_JSON_get_error_code (json);
    227     por.hr.hint = TALER_JSON_get_error_hint (json);
    228     break;
    229   case MHD_HTTP_GONE:
    230     {
    231       bool rq_frac_missing;
    232       bool aq_frac_missing;
    233       struct GNUNET_JSON_Specification spec[] = {
    234         TALER_JSON_spec_slug (
    235           "product_id",
    236           &por.details.gone.product_id),
    237         GNUNET_JSON_spec_uint64 (
    238           "requested_quantity",
    239           &por.details.gone.requested_quantity),
    240         GNUNET_JSON_spec_mark_optional (
    241           GNUNET_JSON_spec_uint32 (
    242             "requested_quantity_frac",
    243             &por.details.gone.requested_quantity_frac),
    244           &rq_frac_missing),
    245         GNUNET_JSON_spec_uint64 (
    246           "available_quantity",
    247           &por.details.gone.available_quantity),
    248         GNUNET_JSON_spec_mark_optional (
    249           GNUNET_JSON_spec_uint32 (
    250             "available_quantity_frac",
    251             &por.details.gone.available_quantity_frac),
    252           &aq_frac_missing),
    253         GNUNET_JSON_spec_mark_optional (
    254           GNUNET_JSON_spec_string (
    255             "unit_requested_quantity",
    256             &por.details.gone.unit_requested_quantity),
    257           NULL),
    258         GNUNET_JSON_spec_mark_optional (
    259           GNUNET_JSON_spec_string (
    260             "unit_available_quantity",
    261             &por.details.gone.unit_available_quantity),
    262           NULL),
    263         GNUNET_JSON_spec_mark_optional (
    264           GNUNET_JSON_spec_timestamp (
    265             "restock_expected",
    266             &por.details.gone.restock_expected),
    267           NULL),
    268         GNUNET_JSON_spec_end ()
    269       };
    270 
    271       if (GNUNET_OK !=
    272           GNUNET_JSON_parse (json,
    273                              spec,
    274                              NULL, NULL))
    275       {
    276         GNUNET_break_op (0);
    277         por.hr.http_status = 0;
    278         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    279       }
    280       else
    281       {
    282         if (rq_frac_missing)
    283           por.details.gone.requested_quantity_frac = 0;
    284         if (aq_frac_missing)
    285           por.details.gone.available_quantity_frac = 0;
    286       }
    287       break;
    288     }
    289   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    290     {
    291       const json_t *jer = NULL;
    292 
    293       por.hr.ec = TALER_JSON_get_error_code (json);
    294       por.hr.hint = TALER_JSON_get_error_hint (json);
    295       jer = json_object_get (json,
    296                              "exchange_rejections");
    297       if ( (NULL != jer) &&
    298            json_is_array (jer) )
    299       {
    300         unsigned int rej_len = (unsigned int) json_array_size (jer);
    301 
    302         if ( (json_array_size (jer) == (size_t) rej_len) &&
    303              (rej_len <= MAX_EXCHANGE_REJECTIONS) )
    304         {
    305           struct TALER_MERCHANT_ExchangeRejectionDetail rejs[
    306             GNUNET_NZL (rej_len)];
    307           bool ok = true;
    308 
    309           memset (rejs, 0, sizeof (rejs));
    310           for (unsigned int i = 0; i < rej_len; i++)
    311           {
    312             struct GNUNET_JSON_Specification rspec[] = {
    313               TALER_JSON_spec_web_url (
    314                 "exchange_url",
    315                 &rejs[i].exchange_url),
    316               TALER_JSON_spec_ec (
    317                 "code",
    318                 &rejs[i].code),
    319               GNUNET_JSON_spec_mark_optional (
    320                 GNUNET_JSON_spec_string (
    321                   "hint",
    322                   &rejs[i].hint),
    323                 NULL),
    324               GNUNET_JSON_spec_end ()
    325             };
    326 
    327             if (GNUNET_OK !=
    328                 GNUNET_JSON_parse (json_array_get (jer, i),
    329                                    rspec,
    330                                    NULL, NULL))
    331             {
    332               GNUNET_break_op (0);
    333               ok = false;
    334               break;
    335             }
    336           }
    337           if (ok)
    338           {
    339             por.details.unavailable_for_legal_reasons
    340             .num_exchange_rejections = rej_len;
    341             por.details.unavailable_for_legal_reasons
    342             .exchange_rejections = rejs;
    343             ppoh->cb (ppoh->cb_cls,
    344                       &por);
    345             TALER_MERCHANT_post_private_orders_cancel (ppoh);
    346             return;
    347           }
    348         }
    349       }
    350       break;
    351     }
    352   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    353     por.hr.ec = TALER_JSON_get_error_code (json);
    354     por.hr.hint = TALER_JSON_get_error_hint (json);
    355     break;
    356   default:
    357     por.hr.ec = TALER_JSON_get_error_code (json);
    358     por.hr.hint = TALER_JSON_get_error_hint (json);
    359     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    360                 "Unexpected response code %u/%d\n",
    361                 (unsigned int) response_code,
    362                 (int) por.hr.ec);
    363     GNUNET_break_op (0);
    364     break;
    365   }
    366   ppoh->cb (ppoh->cb_cls,
    367             &por);
    368   TALER_MERCHANT_post_private_orders_cancel (ppoh);
    369 }
    370 
    371 
    372 struct TALER_MERCHANT_PostPrivateOrdersHandle *
    373 TALER_MERCHANT_post_private_orders_create (
    374   struct GNUNET_CURL_Context *ctx,
    375   const char *url,
    376   const json_t *order)
    377 {
    378   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh;
    379 
    380   ppoh = GNUNET_new (struct TALER_MERCHANT_PostPrivateOrdersHandle);
    381   ppoh->ctx = ctx;
    382   ppoh->base_url = GNUNET_strdup (url);
    383   ppoh->order = json_incref ((json_t *) order);
    384   ppoh->create_token = true;
    385   return ppoh;
    386 }
    387 
    388 
    389 enum GNUNET_GenericReturnValue
    390 TALER_MERCHANT_post_private_orders_set_options_ (
    391   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh,
    392   unsigned int num_options,
    393   const struct TALER_MERCHANT_PostPrivateOrdersOptionValue *options)
    394 {
    395   for (unsigned int i = 0; i < num_options; i++)
    396   {
    397     switch (options[i].option)
    398     {
    399     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_END:
    400       return GNUNET_OK;
    401     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_REFUND_DELAY:
    402       ppoh->refund_delay = options[i].details.refund_delay;
    403       ppoh->refund_delay_set = true;
    404       break;
    405     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_PAYMENT_TARGET:
    406       ppoh->payment_target = options[i].details.payment_target;
    407       break;
    408     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_SESSION_ID:
    409       ppoh->session_id = options[i].details.session_id;
    410       break;
    411     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_CREATE_TOKEN:
    412       ppoh->create_token = options[i].details.create_token;
    413       break;
    414     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_OTP_ID:
    415       ppoh->otp_id = options[i].details.otp_id;
    416       break;
    417     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_CHALLENGE:
    418       ppoh->have_challenge = (NULL != options[i].details.challenge);
    419       if (ppoh->have_challenge)
    420         ppoh->challenge = *options[i].details.challenge;
    421       break;
    422     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_INVENTORY_PRODUCTS:
    423       ppoh->num_inventory_products
    424         = options[i].details.inventory_products.num;
    425       ppoh->inventory_products
    426         = options[i].details.inventory_products.products;
    427       break;
    428     case TALER_MERCHANT_POST_PRIVATE_ORDERS_OPTION_LOCK_UUIDS:
    429       ppoh->num_lock_uuids = options[i].details.lock_uuids.num;
    430       ppoh->lock_uuids = options[i].details.lock_uuids.uuids;
    431       break;
    432     default:
    433       GNUNET_break (0);
    434       return GNUNET_SYSERR;
    435     }
    436   }
    437   return GNUNET_OK;
    438 }
    439 
    440 
    441 enum TALER_ErrorCode
    442 TALER_MERCHANT_post_private_orders_start (
    443   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh,
    444   TALER_MERCHANT_PostPrivateOrdersCallback cb,
    445   TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE *cb_cls)
    446 {
    447   json_t *req;
    448   CURL *eh;
    449 
    450   ppoh->cb = cb;
    451   ppoh->cb_cls = cb_cls;
    452   ppoh->url = TALER_url_join (ppoh->base_url,
    453                               "private/orders",
    454                               NULL);
    455   if (NULL == ppoh->url)
    456     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    457   req = GNUNET_JSON_PACK (
    458     GNUNET_JSON_pack_object_incref ("order",
    459                                     ppoh->order),
    460     GNUNET_JSON_pack_allow_null (
    461       GNUNET_JSON_pack_string ("session_id",
    462                                ppoh->session_id)),
    463     GNUNET_JSON_pack_allow_null (
    464       GNUNET_JSON_pack_string ("payment_target",
    465                                ppoh->payment_target)),
    466     GNUNET_JSON_pack_allow_null (
    467       GNUNET_JSON_pack_string ("otp_id",
    468                                ppoh->otp_id)));
    469   if (ppoh->have_challenge)
    470     GNUNET_assert (0 ==
    471                    json_object_set_new (
    472                      req,
    473                      "challenge",
    474                      GNUNET_JSON_from_data_auto (&ppoh->challenge)));
    475   if (ppoh->refund_delay_set &&
    476       (0 != ppoh->refund_delay.rel_value_us))
    477   {
    478     GNUNET_assert (0 ==
    479                    json_object_set_new (req,
    480                                         "refund_delay",
    481                                         GNUNET_JSON_from_time_rel (
    482                                           ppoh->refund_delay)));
    483   }
    484   if (0 != ppoh->num_inventory_products)
    485   {
    486     json_t *ipa = json_array ();
    487 
    488     GNUNET_assert (NULL != ipa);
    489     for (unsigned int i = 0; i < ppoh->num_inventory_products; i++)
    490     {
    491       json_t *ip;
    492 
    493       {
    494         char unit_quantity_buf[64];
    495 
    496         TALER_MERCHANT_format_quantity_string (
    497           ppoh->inventory_products[i].quantity,
    498           ppoh->inventory_products[i].use_fractional_quantity
    499           ? ppoh->inventory_products[i].quantity_frac
    500           : 0,
    501           unit_quantity_buf,
    502           sizeof (unit_quantity_buf));
    503         ip = GNUNET_JSON_PACK (
    504           GNUNET_JSON_pack_string ("product_id",
    505                                    ppoh->inventory_products[i].product_id),
    506           GNUNET_JSON_pack_string ("unit_quantity",
    507                                    unit_quantity_buf));
    508       }
    509       if (ppoh->inventory_products[i].product_money_pot > 0)
    510       {
    511         GNUNET_assert (
    512           0 ==
    513           json_object_set_new (
    514             ip,
    515             "product_money_pot",
    516             json_integer (
    517               ppoh->inventory_products[i].product_money_pot)));
    518       }
    519       GNUNET_assert (0 ==
    520                      json_array_append_new (ipa,
    521                                             ip));
    522     }
    523     GNUNET_assert (0 ==
    524                    json_object_set_new (req,
    525                                         "inventory_products",
    526                                         ipa));
    527   }
    528   if (0 != ppoh->num_lock_uuids)
    529   {
    530     json_t *ua = json_array ();
    531 
    532     GNUNET_assert (NULL != ua);
    533     for (unsigned int i = 0; i < ppoh->num_lock_uuids; i++)
    534     {
    535       GNUNET_assert (0 ==
    536                      json_array_append_new (ua,
    537                                             json_string (
    538                                               ppoh->lock_uuids[i])));
    539     }
    540     GNUNET_assert (0 ==
    541                    json_object_set_new (req,
    542                                         "lock_uuids",
    543                                         ua));
    544   }
    545   if (! ppoh->create_token)
    546   {
    547     GNUNET_assert (0 ==
    548                    json_object_set_new (req,
    549                                         "create_token",
    550                                         json_boolean (ppoh->create_token)));
    551   }
    552   eh = TALER_MERCHANT_curl_easy_get_ (ppoh->url);
    553   if ( (NULL == eh) ||
    554        (GNUNET_OK !=
    555         TALER_curl_easy_post (&ppoh->post_ctx,
    556                               eh,
    557                               req)) )
    558   {
    559     GNUNET_break (0);
    560     json_decref (req);
    561     if (NULL != eh)
    562       curl_easy_cleanup (eh);
    563     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    564   }
    565   json_decref (req);
    566   ppoh->job = GNUNET_CURL_job_add2 (ppoh->ctx,
    567                                     eh,
    568                                     ppoh->post_ctx.headers,
    569                                     &handle_post_orders_finished,
    570                                     ppoh);
    571   if (NULL == ppoh->job)
    572     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    573   return TALER_EC_NONE;
    574 }
    575 
    576 
    577 void
    578 TALER_MERCHANT_post_private_orders_cancel (
    579   struct TALER_MERCHANT_PostPrivateOrdersHandle *ppoh)
    580 {
    581   if (NULL != ppoh->job)
    582   {
    583     GNUNET_CURL_job_cancel (ppoh->job);
    584     ppoh->job = NULL;
    585   }
    586   TALER_curl_easy_post_finished (&ppoh->post_ctx);
    587   json_decref (ppoh->order);
    588   GNUNET_free (ppoh->url);
    589   GNUNET_free (ppoh->base_url);
    590   GNUNET_free (ppoh);
    591 }
    592 
    593 
    594 /* end of merchant_api_post-private-orders-new.c */