merchant

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

merchant_api_post-private-orders.c (16758B)


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