merchant

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

testing_api_cmd_post_products.c (13756B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (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, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file testing_api_cmd_post_products.c
     21  * @brief command to test POST /products
     22  * @author Christian Grothoff
     23  */
     24 #include "platform.h"
     25 #include <taler/taler_exchange_service.h>
     26 #include <taler/taler_testing_lib.h>
     27 #include "taler_merchant_service.h"
     28 #include "taler_merchant_testing_lib.h"
     29 #include "merchant_api_common.h"
     30 
     31 
     32 /**
     33  * State of a "POST /products" CMD.
     34  */
     35 struct PostProductsState
     36 {
     37 
     38   /**
     39    * Handle for a "POST /products" request.
     40    */
     41   struct TALER_MERCHANT_ProductsPostHandle *iph;
     42 
     43   /**
     44    * The interpreter state.
     45    */
     46   struct TALER_TESTING_Interpreter *is;
     47 
     48   /**
     49    * Base URL of the merchant serving the request.
     50    */
     51   const char *merchant_url;
     52 
     53   /**
     54    * ID of the product to run POST for.
     55    */
     56   const char *product_id;
     57 
     58   /**
     59    * description of the product
     60    */
     61   const char *description;
     62 
     63   /**
     64    * Map from IETF BCP 47 language tags to localized descriptions
     65    */
     66   json_t *description_i18n;
     67 
     68   /**
     69    * unit in which the product is measured (liters, kilograms, packages, etc.)
     70    */
     71   const char *unit;
     72 
     73   /**
     74    * the price for one @a unit of the product
     75    */
     76   struct TALER_Amount price;
     77 
     78   /**
     79    * base64-encoded product image
     80    */
     81   char *image;
     82 
     83   /**
     84    * list of taxes paid by the merchant
     85    */
     86   json_t *taxes;
     87 
     88   /**
     89    * in @e units, -1 to indicate "infinite" (i.e. electronic books)
     90    */
     91   int64_t total_stock;
     92 
     93   /**
     94    * Fractional stock component when fractional quantities are enabled.
     95    */
     96   uint32_t total_stock_frac;
     97 
     98   /**
     99    * Fractional precision level associated with fractional quantities.
    100    */
    101   uint32_t unit_precision_level;
    102 
    103   /**
    104    * whether fractional quantities are allowed for this product.
    105    */
    106   bool unit_allow_fraction;
    107 
    108   /**
    109    * Cached string representation of the stock level.
    110    */
    111   char unit_total_stock[64];
    112 
    113   /**
    114    * set to true if we should use the extended fractional API.
    115    */
    116   bool use_fractional;
    117 
    118   /**
    119    * where the product is in stock
    120    */
    121   json_t *address;
    122 
    123   /**
    124    * Minimum age requirement to use for the product.
    125    */
    126   unsigned int minimum_age;
    127 
    128   /**
    129    * when the next restocking is expected to happen, 0 for unknown,
    130    */
    131   struct GNUNET_TIME_Timestamp next_restock;
    132 
    133   /**
    134    * Expected HTTP response code.
    135    */
    136   unsigned int http_status;
    137 
    138 };
    139 
    140 static void
    141 post_products_update_unit_total_stock (struct PostProductsState *pps)
    142 {
    143   uint64_t stock;
    144   uint32_t frac;
    145 
    146   if (-1 == pps->total_stock)
    147   {
    148     stock = (uint64_t) INT64_MAX;
    149     frac = (uint32_t) INT32_MAX;
    150   }
    151   else
    152   {
    153     stock = (uint64_t) pps->total_stock;
    154     frac = pps->unit_allow_fraction ? pps->total_stock_frac : 0;
    155   }
    156   TALER_MERCHANT_format_stock_string (stock,
    157                                       frac,
    158                                       pps->unit_total_stock,
    159                                       sizeof (pps->unit_total_stock));
    160 }
    161 
    162 
    163 static uint32_t
    164 default_precision_from_unit (const char *unit)
    165 {
    166   struct PrecisionRule
    167   {
    168     const char *unit;
    169     uint32_t precision;
    170   };
    171   static const struct PrecisionRule rules[] = {
    172     { "WeightUnitMg", 0 },
    173     { "SizeUnitMm", 0 },
    174     { "WeightUnitG", 1 },
    175     { "SizeUnitCm", 1 },
    176     { "SurfaceUnitMm2", 1 },
    177     { "VolumeUnitMm3", 1 },
    178     { "WeightUnitOunce", 2 },
    179     { "SizeUnitInch", 2 },
    180     { "SurfaceUnitCm2", 2 },
    181     { "VolumeUnitOunce", 2 },
    182     { "VolumeUnitInch3", 2 },
    183     { "TimeUnitHour", 2 },
    184     { "TimeUnitMonth", 2 },
    185     { "WeightUnitTon", 3 },
    186     { "WeightUnitKg", 3 },
    187     { "WeightUnitPound", 3 },
    188     { "SizeUnitM", 3 },
    189     { "SizeUnitDm", 3 },
    190     { "SizeUnitFoot", 3 },
    191     { "SurfaceUnitDm2", 3 },
    192     { "SurfaceUnitFoot2", 3 },
    193     { "VolumeUnitCm3", 3 },
    194     { "VolumeUnitLitre", 3 },
    195     { "VolumeUnitGallon", 3 },
    196     { "TimeUnitSecond", 3 },
    197     { "TimeUnitMinute", 3 },
    198     { "TimeUnitDay", 3 },
    199     { "TimeUnitWeek", 3 },
    200     { "SurfaceUnitM2", 4 },
    201     { "SurfaceUnitInch2", 4 },
    202     { "TimeUnitYear", 4 },
    203     { "VolumeUnitDm3", 5 },
    204     { "VolumeUnitFoot3", 5 },
    205     { "VolumeUnitM3", 6 }
    206   };
    207 
    208   const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
    209   if (NULL == unit)
    210     return 0;
    211 
    212   for (size_t i = 0; i<rules_len; i++)
    213     if (0 == strcmp (unit,
    214                      rules[i].unit))
    215       return rules[i].precision;
    216   return 0;
    217 }
    218 
    219 
    220 /**
    221  * Callback for a POST /products operation.
    222  *
    223  * @param cls closure for this function
    224  * @param hr response being processed
    225  */
    226 static void
    227 post_products_cb (void *cls,
    228                   const struct TALER_MERCHANT_HttpResponse *hr)
    229 {
    230   struct PostProductsState *pis = cls;
    231 
    232   pis->iph = NULL;
    233   if (pis->http_status != hr->http_status)
    234   {
    235     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    236                 "Unexpected response code %u (%d) to command %s\n",
    237                 hr->http_status,
    238                 (int) hr->ec,
    239                 TALER_TESTING_interpreter_get_current_label (pis->is));
    240     TALER_TESTING_interpreter_fail (pis->is);
    241     return;
    242   }
    243   switch (hr->http_status)
    244   {
    245   case MHD_HTTP_NO_CONTENT:
    246     break;
    247   case MHD_HTTP_UNAUTHORIZED:
    248     break;
    249   case MHD_HTTP_FORBIDDEN:
    250     break;
    251   case MHD_HTTP_NOT_FOUND:
    252     break;
    253   case MHD_HTTP_CONFLICT:
    254     break;
    255   default:
    256     GNUNET_break (0);
    257     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    258                 "Unhandled HTTP status %u for POST /products.\n",
    259                 hr->http_status);
    260   }
    261   TALER_TESTING_interpreter_next (pis->is);
    262 }
    263 
    264 
    265 /**
    266  * Run the "POST /products" CMD.
    267  *
    268  *
    269  * @param cls closure.
    270  * @param cmd command being run now.
    271  * @param is interpreter state.
    272  */
    273 static void
    274 post_products_run (void *cls,
    275                    const struct TALER_TESTING_Command *cmd,
    276                    struct TALER_TESTING_Interpreter *is)
    277 {
    278   struct PostProductsState *pis = cls;
    279 
    280   pis->is = is;
    281   if (pis->use_fractional)
    282   {
    283     pis->iph = TALER_MERCHANT_products_post4 (
    284       TALER_TESTING_interpreter_get_context (is),
    285       pis->merchant_url,
    286       pis->product_id,
    287       pis->description,
    288       pis->description_i18n,
    289       pis->unit,
    290       &pis->price,
    291       1,
    292       pis->image,
    293       pis->taxes,
    294       pis->total_stock,
    295       pis->total_stock_frac,
    296       pis->unit_allow_fraction,
    297       pis->use_fractional
    298       ? &pis->unit_precision_level
    299       : NULL,
    300       pis->address,
    301       pis->next_restock,
    302       pis->minimum_age,
    303       0,
    304       NULL,
    305       &post_products_cb,
    306       pis);
    307   }
    308   else
    309   {
    310     pis->iph = TALER_MERCHANT_products_post2 (
    311       TALER_TESTING_interpreter_get_context (is),
    312       pis->merchant_url,
    313       pis->product_id,
    314       pis->description,
    315       pis->description_i18n,
    316       pis->unit,
    317       &pis->price,
    318       pis->image,
    319       pis->taxes,
    320       pis->total_stock,
    321       pis->address,
    322       pis->next_restock,
    323       pis->minimum_age,
    324       &post_products_cb,
    325       pis);
    326   }
    327   GNUNET_assert (NULL != pis->iph);
    328 }
    329 
    330 
    331 /**
    332  * Offers information from the POST /products CMD state to other
    333  * commands.
    334  *
    335  * @param cls closure
    336  * @param[out] ret result (could be anything)
    337  * @param trait name of the trait
    338  * @param index index number of the object to extract.
    339  * @return #GNUNET_OK on success
    340  */
    341 static enum GNUNET_GenericReturnValue
    342 post_products_traits (void *cls,
    343                       const void **ret,
    344                       const char *trait,
    345                       unsigned int index)
    346 {
    347   struct PostProductsState *pps = cls;
    348   struct TALER_TESTING_Trait traits[] = {
    349     TALER_TESTING_make_trait_product_description (pps->description),
    350     TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
    351     TALER_TESTING_make_trait_product_unit (pps->unit),
    352     TALER_TESTING_make_trait_amount (&pps->price),
    353     TALER_TESTING_make_trait_product_image (pps->image),
    354     TALER_TESTING_make_trait_taxes (pps->taxes),
    355     TALER_TESTING_make_trait_product_stock (&pps->total_stock),
    356     TALER_TESTING_make_trait_product_unit_total_stock (
    357       pps->unit_total_stock),
    358     TALER_TESTING_make_trait_product_unit_precision_level (
    359       &pps->unit_precision_level),
    360     TALER_TESTING_make_trait_product_unit_allow_fraction (
    361       &pps->unit_allow_fraction),
    362     TALER_TESTING_make_trait_address (pps->address),
    363     TALER_TESTING_make_trait_timestamp (0,
    364                                         &pps->next_restock),
    365     TALER_TESTING_make_trait_product_id (pps->product_id),
    366     TALER_TESTING_trait_end (),
    367   };
    368 
    369   return TALER_TESTING_get_trait (traits,
    370                                   ret,
    371                                   trait,
    372                                   index);
    373 }
    374 
    375 
    376 /**
    377  * Free the state of a "POST product" CMD, and possibly
    378  * cancel a pending operation thereof.
    379  *
    380  * @param cls closure.
    381  * @param cmd command being run.
    382  */
    383 static void
    384 post_products_cleanup (void *cls,
    385                        const struct TALER_TESTING_Command *cmd)
    386 {
    387   struct PostProductsState *pis = cls;
    388 
    389   if (NULL != pis->iph)
    390   {
    391     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    392                 "POST /products operation did not complete\n");
    393     TALER_MERCHANT_products_post_cancel (pis->iph);
    394   }
    395   json_decref (pis->description_i18n);
    396   GNUNET_free (pis->image);
    397   json_decref (pis->taxes);
    398   json_decref (pis->address);
    399   GNUNET_free (pis);
    400 }
    401 
    402 
    403 struct TALER_TESTING_Command
    404 TALER_TESTING_cmd_merchant_post_products2 (
    405   const char *label,
    406   const char *merchant_url,
    407   const char *product_id,
    408   const char *description,
    409   json_t *description_i18n,
    410   const char *unit,
    411   const char *price,
    412   const char *image,
    413   json_t *taxes,
    414   int64_t total_stock,
    415   uint32_t minimum_age,
    416   json_t *address,
    417   struct GNUNET_TIME_Timestamp next_restock,
    418   unsigned int http_status)
    419 {
    420   struct PostProductsState *pis;
    421 
    422   GNUNET_assert ((NULL == taxes) ||
    423                  json_is_array (taxes));
    424   GNUNET_assert ((NULL == description_i18n) ||
    425                  json_is_object (description_i18n));
    426   pis = GNUNET_new (struct PostProductsState);
    427   pis->merchant_url = merchant_url;
    428   pis->product_id = product_id;
    429   pis->http_status = http_status;
    430   pis->description = description;
    431   pis->description_i18n = description_i18n; /* ownership taken */
    432   pis->unit = unit;
    433   pis->unit_precision_level = default_precision_from_unit (unit);
    434   GNUNET_assert (GNUNET_OK ==
    435                  TALER_string_to_amount (price,
    436                                          &pis->price));
    437   pis->image = GNUNET_strdup (image);
    438   pis->taxes = taxes; /* ownership taken */
    439   pis->total_stock = total_stock;
    440   pis->total_stock_frac = 0;
    441   pis->unit_allow_fraction = false;
    442   pis->use_fractional = false;
    443   pis->minimum_age = minimum_age;
    444   pis->address = address; /* ownership taken */
    445   pis->next_restock = next_restock;
    446   post_products_update_unit_total_stock (pis);
    447   {
    448     struct TALER_TESTING_Command cmd = {
    449       .cls = pis,
    450       .label = label,
    451       .run = &post_products_run,
    452       .cleanup = &post_products_cleanup,
    453       .traits = &post_products_traits
    454     };
    455 
    456     return cmd;
    457   }
    458 }
    459 
    460 
    461 struct TALER_TESTING_Command
    462 TALER_TESTING_cmd_merchant_post_products3 (
    463   const char *label,
    464   const char *merchant_url,
    465   const char *product_id,
    466   const char *description,
    467   json_t *description_i18n,
    468   const char *unit,
    469   const char *price,
    470   const char *image,
    471   json_t *taxes,
    472   int64_t total_stock,
    473   uint32_t total_stock_frac,
    474   bool unit_allow_fraction,
    475   uint32_t minimum_age,
    476   json_t *address,
    477   struct GNUNET_TIME_Timestamp next_restock,
    478   unsigned int http_status)
    479 {
    480   struct TALER_TESTING_Command cmd;
    481 
    482   cmd = TALER_TESTING_cmd_merchant_post_products2 (label,
    483                                                    merchant_url,
    484                                                    product_id,
    485                                                    description,
    486                                                    description_i18n,
    487                                                    unit,
    488                                                    price,
    489                                                    image,
    490                                                    taxes,
    491                                                    total_stock,
    492                                                    minimum_age,
    493                                                    address,
    494                                                    next_restock,
    495                                                    http_status);
    496   {
    497     struct PostProductsState *pis = cmd.cls;
    498 
    499     pis->total_stock_frac = total_stock_frac;
    500     pis->unit_allow_fraction = unit_allow_fraction;
    501     pis->use_fractional = true;
    502     post_products_update_unit_total_stock (pis);
    503   }
    504   return cmd;
    505 }
    506 
    507 
    508 struct TALER_TESTING_Command
    509 TALER_TESTING_cmd_merchant_post_products (
    510   const char *label,
    511   const char *merchant_url,
    512   const char *product_id,
    513   const char *description,
    514   const char *price,
    515   unsigned int http_status)
    516 {
    517   return TALER_TESTING_cmd_merchant_post_products2 (
    518     label,
    519     merchant_url,
    520     product_id,
    521     description,
    522     json_pack ("{s:s}", "en", description),
    523     "test-unit",
    524     price,
    525     "",
    526     json_array (),
    527     4, /* total stock */
    528     0, /* minimum age */
    529     json_pack ("{s:s}", "street", "my street"),
    530     GNUNET_TIME_UNIT_ZERO_TS,
    531     http_status);
    532 }
    533 
    534 
    535 /* end of testing_api_cmd_post_products.c */