merchant

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

merchant_api_post_products.c (14019B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Lesser General Public License as
      7   published by the Free Software Foundation; either version 2.1,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU Lesser General Public License for more details.
     14 
     15   You should have received a copy of the GNU Lesser General
     16   Public License along with TALER; see the file COPYING.LGPL.
     17   If not, see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file merchant_api_post_products.c
     21  * @brief Implementation of the POST /products request
     22  *        of the merchant's HTTP API
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include <curl/curl.h>
     27 #include <jansson.h>
     28 #include <microhttpd.h> /* just for HTTP status codes */
     29 #include <gnunet/gnunet_util_lib.h>
     30 #include "taler_merchant_service.h"
     31 #include "merchant_api_curl_defaults.h"
     32 #include "merchant_api_common.h"
     33 #include <taler/taler_json_lib.h>
     34 #include <taler/taler_curl_lib.h>
     35 
     36 
     37 /**
     38  * Handle for a POST /products/$ID operation.
     39  */
     40 struct TALER_MERCHANT_ProductsPostHandle
     41 {
     42 
     43   /**
     44    * The url for this request.
     45    */
     46   char *url;
     47 
     48   /**
     49    * Handle for the request.
     50    */
     51   struct GNUNET_CURL_Job *job;
     52 
     53   /**
     54    * Function to call with the result.
     55    */
     56   TALER_MERCHANT_ProductsPostCallback cb;
     57 
     58   /**
     59    * Closure for @a cb.
     60    */
     61   void *cb_cls;
     62 
     63   /**
     64    * Reference to the execution context.
     65    */
     66   struct GNUNET_CURL_Context *ctx;
     67 
     68   /**
     69    * Minor context that holds body and headers.
     70    */
     71   struct TALER_CURL_PostContext post_ctx;
     72 
     73 };
     74 
     75 
     76 /**
     77  * Function called when we're done processing the
     78  * HTTP POST /products request.
     79  *
     80  * @param cls the `struct TALER_MERCHANT_ProductsPostHandle`
     81  * @param response_code HTTP response code, 0 on error
     82  * @param response response body, NULL if not in JSON
     83  */
     84 static void
     85 handle_post_products_finished (void *cls,
     86                                long response_code,
     87                                const void *response)
     88 {
     89   struct TALER_MERCHANT_ProductsPostHandle *pph = cls;
     90   const json_t *json = response;
     91   struct TALER_MERCHANT_HttpResponse hr = {
     92     .http_status = (unsigned int) response_code,
     93     .reply = json
     94   };
     95 
     96   pph->job = NULL;
     97   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     98               "POST /products completed with response code %u\n",
     99               (unsigned int) response_code);
    100   switch (response_code)
    101   {
    102   case 0:
    103     hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    104     break;
    105   case MHD_HTTP_NO_CONTENT:
    106     break;
    107   case MHD_HTTP_BAD_REQUEST:
    108     hr.ec = TALER_JSON_get_error_code (json);
    109     hr.hint = TALER_JSON_get_error_hint (json);
    110     /* This should never happen, either us
    111      * or the merchant is buggy (or API version conflict);
    112      * just pass JSON reply to the application */
    113     break;
    114   case MHD_HTTP_UNAUTHORIZED:
    115     hr.ec = TALER_JSON_get_error_code (json);
    116     hr.hint = TALER_JSON_get_error_hint (json);
    117     /* Nothing really to verify, merchant says we need to authenticate. */
    118     break;
    119   case MHD_HTTP_FORBIDDEN:
    120     hr.ec = TALER_JSON_get_error_code (json);
    121     hr.hint = TALER_JSON_get_error_hint (json);
    122     /* Nothing really to verify, merchant says we tried to abort the payment
    123      * after it was successful. We should pass the JSON reply to the
    124      * application */
    125     break;
    126   case MHD_HTTP_NOT_FOUND:
    127     hr.ec = TALER_JSON_get_error_code (json);
    128     hr.hint = TALER_JSON_get_error_hint (json);
    129     /* Nothing really to verify, this should never
    130        happen, we should pass the JSON reply to the
    131        application */
    132     break;
    133   case MHD_HTTP_CONFLICT:
    134     hr.ec = TALER_JSON_get_error_code (json);
    135     hr.hint = TALER_JSON_get_error_hint (json);
    136     break;
    137   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    138     hr.ec = TALER_JSON_get_error_code (json);
    139     hr.hint = TALER_JSON_get_error_hint (json);
    140     /* Server had an internal issue; we should retry,
    141        but this API leaves this to the application */
    142     break;
    143   default:
    144     TALER_MERCHANT_parse_error_details_ (json,
    145                                          response_code,
    146                                          &hr);
    147     /* unexpected response code */
    148     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    149                 "Unexpected response code %u/%d\n",
    150                 (unsigned int) response_code,
    151                 (int) hr.ec);
    152     GNUNET_break_op (0);
    153     break;
    154   }
    155   pph->cb (pph->cb_cls,
    156            &hr);
    157   TALER_MERCHANT_products_post_cancel (pph);
    158 }
    159 
    160 
    161 struct TALER_MERCHANT_ProductsPostHandle *
    162 TALER_MERCHANT_products_post4 (
    163   struct GNUNET_CURL_Context *ctx,
    164   const char *backend_url,
    165   const char *product_id,
    166   const char *description,
    167   const json_t *description_i18n,
    168   const char *unit,
    169   const struct TALER_Amount *unit_prices,
    170   size_t unit_price_len,
    171   const char *image,
    172   const json_t *taxes,
    173   int64_t total_stock,
    174   uint32_t total_stock_frac,
    175   bool unit_allow_fraction,
    176   const uint32_t *unit_precision_level,
    177   const json_t *address,
    178   struct GNUNET_TIME_Timestamp next_restock,
    179   uint32_t minimum_age,
    180   unsigned int num_cats,
    181   const uint64_t *cats,
    182   TALER_MERCHANT_ProductsPostCallback cb,
    183   void *cb_cls)
    184 {
    185   struct TALER_MERCHANT_ProductsPostHandle *pph;
    186   json_t *req_obj;
    187   json_t *categories;
    188   char unit_total_stock_buf[64];
    189 
    190   TALER_MERCHANT_format_stock_string (total_stock,
    191                                       total_stock_frac,
    192                                       unit_total_stock_buf,
    193                                       sizeof (unit_total_stock_buf));
    194 
    195   if (0 == num_cats)
    196   {
    197     categories = NULL;
    198   }
    199   else
    200   {
    201     categories = json_array ();
    202     GNUNET_assert (NULL != categories);
    203     for (unsigned int i = 0; i<num_cats; i++)
    204       GNUNET_assert (0 ==
    205                      json_array_append_new (categories,
    206                                             json_integer (cats[i])));
    207   }
    208   {
    209     req_obj = GNUNET_JSON_PACK (
    210       GNUNET_JSON_pack_string ("product_id",
    211                                product_id),
    212       /* FIXME: once we move to the new-style API,
    213          allow applications to set the product name properly! */
    214       GNUNET_JSON_pack_string ("product_name",
    215                                description),
    216       GNUNET_JSON_pack_string ("description",
    217                                description),
    218       GNUNET_JSON_pack_allow_null (
    219         GNUNET_JSON_pack_object_incref ("description_i18n",
    220                                         (json_t *) description_i18n)),
    221       GNUNET_JSON_pack_allow_null (
    222         GNUNET_JSON_pack_array_steal ("categories",
    223                                       categories)),
    224       GNUNET_JSON_pack_string ("unit",
    225                                unit),
    226       TALER_JSON_pack_amount_array ("unit_price",
    227                                     unit_price_len,
    228                                     unit_prices),
    229       GNUNET_JSON_pack_string ("image",
    230                                image),
    231       GNUNET_JSON_pack_allow_null (
    232         GNUNET_JSON_pack_array_incref ("taxes",
    233                                        (json_t *) taxes)),
    234       GNUNET_JSON_pack_string ("unit_total_stock",
    235                                unit_total_stock_buf),
    236       GNUNET_JSON_pack_bool ("unit_allow_fraction",
    237                              unit_allow_fraction),
    238       GNUNET_JSON_pack_allow_null (
    239         GNUNET_JSON_pack_uint64 ("minimum_age",
    240                                  minimum_age)),
    241       GNUNET_JSON_pack_allow_null (
    242         GNUNET_JSON_pack_object_incref ("address",
    243                                         (json_t *) address)),
    244       GNUNET_JSON_pack_allow_null (
    245         GNUNET_JSON_pack_timestamp ("next_restock",
    246                                     next_restock)));
    247   }
    248   if (NULL != unit_precision_level)
    249   {
    250     GNUNET_assert (0 ==
    251                    json_object_set_new (req_obj,
    252                                         "unit_precision_level",
    253                                         json_integer (
    254                                           *unit_precision_level)));
    255   }
    256   if (! unit_allow_fraction)
    257   {
    258     GNUNET_assert (0 ==
    259                    json_object_del (req_obj,
    260                                     "unit_allow_fraction"));
    261     if (NULL != unit_precision_level)
    262       GNUNET_assert (0 ==
    263                      json_object_del (req_obj,
    264                                       "unit_precision_level"));
    265   }
    266   pph = GNUNET_new (struct TALER_MERCHANT_ProductsPostHandle);
    267   pph->ctx = ctx;
    268   pph->cb = cb;
    269   pph->cb_cls = cb_cls;
    270   pph->url = TALER_url_join (backend_url,
    271                              "private/products",
    272                              NULL);
    273   if (NULL == pph->url)
    274   {
    275     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    276                 "Could not construct request URL.\n");
    277     json_decref (req_obj);
    278     GNUNET_free (pph);
    279     return NULL;
    280   }
    281   {
    282     CURL *eh;
    283 
    284     eh = TALER_MERCHANT_curl_easy_get_ (pph->url);
    285     GNUNET_assert (GNUNET_OK ==
    286                    TALER_curl_easy_post (&pph->post_ctx,
    287                                          eh,
    288                                          req_obj));
    289     json_decref (req_obj);
    290     pph->job = GNUNET_CURL_job_add2 (ctx,
    291                                      eh,
    292                                      pph->post_ctx.headers,
    293                                      &handle_post_products_finished,
    294                                      pph);
    295     GNUNET_assert (NULL != pph->job);
    296   }
    297   return pph;
    298 }
    299 
    300 
    301 struct TALER_MERCHANT_ProductsPostHandle *
    302 TALER_MERCHANT_products_post3 (
    303   struct GNUNET_CURL_Context *ctx,
    304   const char *backend_url,
    305   const char *product_id,
    306   const char *description,
    307   const json_t *description_i18n,
    308   const char *unit,
    309   const struct TALER_Amount *price,
    310   const char *image,
    311   const json_t *taxes,
    312   int64_t total_stock,
    313   const json_t *address,
    314   struct GNUNET_TIME_Timestamp next_restock,
    315   uint32_t minimum_age,
    316   unsigned int num_cats,
    317   const uint64_t *cats,
    318   TALER_MERCHANT_ProductsPostCallback cb,
    319   void *cb_cls)
    320 {
    321   return TALER_MERCHANT_products_post4 (ctx,
    322                                         backend_url,
    323                                         product_id,
    324                                         description,
    325                                         description_i18n,
    326                                         unit,
    327                                         price,
    328                                         1,
    329                                         image,
    330                                         taxes,
    331                                         total_stock,
    332                                         0,
    333                                         false,
    334                                         NULL,
    335                                         address,
    336                                         next_restock,
    337                                         minimum_age,
    338                                         num_cats,
    339                                         cats,
    340                                         cb,
    341                                         cb_cls);
    342 }
    343 
    344 
    345 struct TALER_MERCHANT_ProductsPostHandle *
    346 TALER_MERCHANT_products_post2 (
    347   struct GNUNET_CURL_Context *ctx,
    348   const char *backend_url,
    349   const char *product_id,
    350   const char *description,
    351   const json_t *description_i18n,
    352   const char *unit,
    353   const struct TALER_Amount *price,
    354   const char *image,
    355   const json_t *taxes,
    356   int64_t total_stock,
    357   const json_t *address,
    358   struct GNUNET_TIME_Timestamp next_restock,
    359   uint32_t minimum_age,
    360   TALER_MERCHANT_ProductsPostCallback cb,
    361   void *cb_cls)
    362 {
    363   return TALER_MERCHANT_products_post3 (ctx,
    364                                         backend_url,
    365                                         product_id,
    366                                         description,
    367                                         description_i18n,
    368                                         unit,
    369                                         price,
    370                                         image,
    371                                         taxes,
    372                                         total_stock,
    373                                         address,
    374                                         next_restock,
    375                                         minimum_age,
    376                                         0,
    377                                         NULL,
    378                                         cb,
    379                                         cb_cls);
    380 }
    381 
    382 
    383 struct TALER_MERCHANT_ProductsPostHandle *
    384 TALER_MERCHANT_products_post (
    385   struct GNUNET_CURL_Context *ctx,
    386   const char *backend_url,
    387   const char *product_id,
    388   const char *description,
    389   const json_t *description_i18n,
    390   const char *unit,
    391   const struct TALER_Amount *price,
    392   const char *image,
    393   const json_t *taxes,
    394   int64_t total_stock,
    395   const json_t *address,
    396   struct GNUNET_TIME_Timestamp next_restock,
    397   TALER_MERCHANT_ProductsPostCallback cb,
    398   void *cb_cls)
    399 {
    400   return TALER_MERCHANT_products_post2 (ctx,
    401                                         backend_url,
    402                                         product_id,
    403                                         description,
    404                                         description_i18n,
    405                                         unit,
    406                                         price,
    407                                         image,
    408                                         taxes,
    409                                         total_stock,
    410                                         address,
    411                                         next_restock,
    412                                         0,
    413                                         cb,
    414                                         cb_cls);
    415 }
    416 
    417 
    418 void
    419 TALER_MERCHANT_products_post_cancel (
    420   struct TALER_MERCHANT_ProductsPostHandle *pph)
    421 {
    422   if (NULL != pph->job)
    423   {
    424     GNUNET_CURL_job_cancel (pph->job);
    425     pph->job = NULL;
    426   }
    427   TALER_curl_easy_post_finished (&pph->post_ctx);
    428   GNUNET_free (pph->url);
    429   GNUNET_free (pph);
    430 }
    431 
    432 
    433 /* end of merchant_api_post_products.c */