merchant

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

testing_api_cmd_post_products.c (17708B)


      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 "taler/platform.h"
     25 #include <taler/taler_exchange_service.h>
     26 #include <taler/taler_testing_lib.h>
     27 #include "taler/taler_merchant_service.h"
     28 #include "taler/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    * Array of unit prices (may point to @e price for single-entry usage).
     80    */
     81   struct TALER_Amount *unit_prices;
     82 
     83   /**
     84    * Number of entries in @e unit_prices.
     85    */
     86   size_t unit_prices_len;
     87 
     88   /**
     89    * True if @e unit_prices must be freed.
     90    */
     91   bool owns_unit_prices;
     92 
     93   /**
     94    * True if we should send an explicit unit_price array.
     95    */
     96   bool use_unit_price_array;
     97 
     98   /**
     99    * base64-encoded product image
    100    */
    101   char *image;
    102 
    103   /**
    104    * list of taxes paid by the merchant
    105    */
    106   json_t *taxes;
    107 
    108   /**
    109    * in @e units, -1 to indicate "infinite" (i.e. electronic books)
    110    */
    111   int64_t total_stock;
    112 
    113   /**
    114    * Fractional stock component when fractional quantities are enabled.
    115    */
    116   uint32_t total_stock_frac;
    117 
    118   /**
    119    * Fractional precision level associated with fractional quantities.
    120    */
    121   uint32_t unit_precision_level;
    122 
    123   /**
    124    * whether fractional quantities are allowed for this product.
    125    */
    126   bool unit_allow_fraction;
    127 
    128   /**
    129    * Cached string representation of the stock level.
    130    */
    131   char unit_total_stock[64];
    132 
    133   /**
    134    * set to true if we should use the extended fractional API.
    135    */
    136   bool use_fractional;
    137 
    138   /**
    139    * where the product is in stock
    140    */
    141   json_t *address;
    142 
    143   /**
    144    * Minimum age requirement to use for the product.
    145    */
    146   unsigned int minimum_age;
    147 
    148   /**
    149    * when the next restocking is expected to happen, 0 for unknown,
    150    */
    151   struct GNUNET_TIME_Timestamp next_restock;
    152 
    153   /**
    154    * Categories array length.
    155    */
    156   unsigned int num_cats;
    157 
    158   /**
    159    * Categories array.
    160    */
    161   uint64_t *cats;
    162 
    163   /**
    164    * Expected HTTP response code.
    165    */
    166   unsigned int http_status;
    167 
    168 };
    169 
    170 static void
    171 post_products_update_unit_total_stock (struct PostProductsState *pps)
    172 {
    173   if (-1 == pps->total_stock)
    174   {
    175     pps->total_stock = INT64_MAX;
    176     pps->total_stock_frac = INT32_MAX;
    177   }
    178   else if (! pps->unit_allow_fraction)
    179   {
    180     pps->total_stock_frac = 0;
    181   }
    182   TALER_MERCHANT_format_stock_string ((uint64_t) pps->total_stock,
    183                                       pps->total_stock_frac,
    184                                       pps->unit_total_stock,
    185                                       sizeof (pps->unit_total_stock));
    186 }
    187 
    188 
    189 static uint32_t
    190 default_precision_from_unit (const char *unit)
    191 {
    192   struct PrecisionRule
    193   {
    194     const char *unit;
    195     uint32_t precision;
    196   };
    197   static const struct PrecisionRule rules[] = {
    198     { "WeightUnitMg", 0 },
    199     { "SizeUnitMm", 0 },
    200     { "WeightUnitG", 1 },
    201     { "SizeUnitCm", 1 },
    202     { "SurfaceUnitMm2", 1 },
    203     { "VolumeUnitMm3", 1 },
    204     { "WeightUnitOunce", 2 },
    205     { "SizeUnitInch", 2 },
    206     { "SurfaceUnitCm2", 2 },
    207     { "VolumeUnitOunce", 2 },
    208     { "VolumeUnitInch3", 2 },
    209     { "TimeUnitHour", 2 },
    210     { "TimeUnitMonth", 2 },
    211     { "WeightUnitTon", 3 },
    212     { "WeightUnitKg", 3 },
    213     { "WeightUnitPound", 3 },
    214     { "SizeUnitM", 3 },
    215     { "SizeUnitDm", 3 },
    216     { "SizeUnitFoot", 3 },
    217     { "SurfaceUnitDm2", 3 },
    218     { "SurfaceUnitFoot2", 3 },
    219     { "VolumeUnitCm3", 3 },
    220     { "VolumeUnitLitre", 3 },
    221     { "VolumeUnitGallon", 3 },
    222     { "TimeUnitSecond", 3 },
    223     { "TimeUnitMinute", 3 },
    224     { "TimeUnitDay", 3 },
    225     { "TimeUnitWeek", 3 },
    226     { "SurfaceUnitM2", 4 },
    227     { "SurfaceUnitInch2", 4 },
    228     { "TimeUnitYear", 4 },
    229     { "VolumeUnitDm3", 5 },
    230     { "VolumeUnitFoot3", 5 },
    231     { "VolumeUnitM3", 6 }
    232   };
    233 
    234   const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
    235   if (NULL == unit)
    236     return 0;
    237 
    238   for (size_t i = 0; i<rules_len; i++)
    239     if (0 == strcmp (unit,
    240                      rules[i].unit))
    241       return rules[i].precision;
    242   return 0;
    243 }
    244 
    245 
    246 /**
    247  * Callback for a POST /products operation.
    248  *
    249  * @param cls closure for this function
    250  * @param hr response being processed
    251  */
    252 static void
    253 post_products_cb (void *cls,
    254                   const struct TALER_MERCHANT_HttpResponse *hr)
    255 {
    256   struct PostProductsState *pis = cls;
    257 
    258   pis->iph = NULL;
    259   if (pis->http_status != hr->http_status)
    260   {
    261     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    262                 "Unexpected response code %u (%d) to command %s\n",
    263                 hr->http_status,
    264                 (int) hr->ec,
    265                 TALER_TESTING_interpreter_get_current_label (pis->is));
    266     TALER_TESTING_interpreter_fail (pis->is);
    267     return;
    268   }
    269   switch (hr->http_status)
    270   {
    271   case MHD_HTTP_NO_CONTENT:
    272     break;
    273   case MHD_HTTP_UNAUTHORIZED:
    274     break;
    275   case MHD_HTTP_FORBIDDEN:
    276     break;
    277   case MHD_HTTP_NOT_FOUND:
    278     break;
    279   case MHD_HTTP_CONFLICT:
    280     break;
    281   default:
    282     GNUNET_break (0);
    283     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    284                 "Unhandled HTTP status %u for POST /products.\n",
    285                 hr->http_status);
    286   }
    287   TALER_TESTING_interpreter_next (pis->is);
    288 }
    289 
    290 
    291 /**
    292  * Run the "POST /products" CMD.
    293  *
    294  *
    295  * @param cls closure.
    296  * @param cmd command being run now.
    297  * @param is interpreter state.
    298  */
    299 static void
    300 post_products_run (void *cls,
    301                    const struct TALER_TESTING_Command *cmd,
    302                    struct TALER_TESTING_Interpreter *is)
    303 {
    304   struct PostProductsState *pis = cls;
    305 
    306   pis->is = is;
    307   if (pis->use_fractional ||
    308       pis->use_unit_price_array ||
    309       (0 < pis->num_cats))
    310   {
    311     pis->iph = TALER_MERCHANT_products_post4 (
    312       TALER_TESTING_interpreter_get_context (is),
    313       pis->merchant_url,
    314       pis->product_id,
    315       pis->description,
    316       pis->description_i18n,
    317       pis->unit,
    318       pis->unit_prices,
    319       pis->unit_prices_len,
    320       pis->image,
    321       pis->taxes,
    322       pis->total_stock,
    323       pis->total_stock_frac,
    324       pis->unit_allow_fraction,
    325       pis->use_fractional
    326       ? &pis->unit_precision_level
    327       : NULL,
    328       pis->address,
    329       pis->next_restock,
    330       pis->minimum_age,
    331       pis->num_cats,
    332       pis->cats,
    333       &post_products_cb,
    334       pis);
    335   }
    336   else
    337   {
    338     pis->iph = TALER_MERCHANT_products_post2 (
    339       TALER_TESTING_interpreter_get_context (is),
    340       pis->merchant_url,
    341       pis->product_id,
    342       pis->description,
    343       pis->description_i18n,
    344       pis->unit,
    345       &pis->price,
    346       pis->image,
    347       pis->taxes,
    348       pis->total_stock,
    349       pis->address,
    350       pis->next_restock,
    351       pis->minimum_age,
    352       &post_products_cb,
    353       pis);
    354   }
    355   GNUNET_assert (NULL != pis->iph);
    356 }
    357 
    358 
    359 /**
    360  * Offers information from the POST /products CMD state to other
    361  * commands.
    362  *
    363  * @param cls closure
    364  * @param[out] ret result (could be anything)
    365  * @param trait name of the trait
    366  * @param index index number of the object to extract.
    367  * @return #GNUNET_OK on success
    368  */
    369 static enum GNUNET_GenericReturnValue
    370 post_products_traits (void *cls,
    371                       const void **ret,
    372                       const char *trait,
    373                       unsigned int index)
    374 {
    375   struct PostProductsState *pps = cls;
    376   struct TALER_TESTING_Trait traits[] = {
    377     TALER_TESTING_make_trait_product_description (pps->description),
    378     TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
    379     TALER_TESTING_make_trait_product_unit (pps->unit),
    380     TALER_TESTING_make_trait_amount (&pps->price),
    381     TALER_TESTING_make_trait_product_image (pps->image),
    382     TALER_TESTING_make_trait_taxes (pps->taxes),
    383     TALER_TESTING_make_trait_product_stock (&pps->total_stock),
    384     TALER_TESTING_make_trait_product_unit_total_stock (
    385       pps->unit_total_stock),
    386     TALER_TESTING_make_trait_product_unit_precision_level (
    387       &pps->unit_precision_level),
    388     TALER_TESTING_make_trait_product_unit_allow_fraction (
    389       &pps->unit_allow_fraction),
    390     TALER_TESTING_make_trait_address (pps->address),
    391     TALER_TESTING_make_trait_timestamp (0,
    392                                         &pps->next_restock),
    393     TALER_TESTING_make_trait_product_id (pps->product_id),
    394     TALER_TESTING_trait_end (),
    395   };
    396 
    397   return TALER_TESTING_get_trait (traits,
    398                                   ret,
    399                                   trait,
    400                                   index);
    401 }
    402 
    403 
    404 /**
    405  * Free the state of a "POST product" CMD, and possibly
    406  * cancel a pending operation thereof.
    407  *
    408  * @param cls closure.
    409  * @param cmd command being run.
    410  */
    411 static void
    412 post_products_cleanup (void *cls,
    413                        const struct TALER_TESTING_Command *cmd)
    414 {
    415   struct PostProductsState *pis = cls;
    416 
    417   if (NULL != pis->iph)
    418   {
    419     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    420                 "POST /products operation did not complete\n");
    421     TALER_MERCHANT_products_post_cancel (pis->iph);
    422   }
    423   if (pis->owns_unit_prices)
    424     GNUNET_free (pis->unit_prices);
    425   json_decref (pis->description_i18n);
    426   GNUNET_free (pis->image);
    427   json_decref (pis->taxes);
    428   json_decref (pis->address);
    429   GNUNET_free (pis->cats);
    430   GNUNET_free (pis);
    431 }
    432 
    433 
    434 struct TALER_TESTING_Command
    435 TALER_TESTING_cmd_merchant_post_products2 (
    436   const char *label,
    437   const char *merchant_url,
    438   const char *product_id,
    439   const char *description,
    440   json_t *description_i18n,
    441   const char *unit,
    442   const char *price,
    443   const char *image,
    444   json_t *taxes,
    445   int64_t total_stock,
    446   uint32_t minimum_age,
    447   json_t *address,
    448   struct GNUNET_TIME_Timestamp next_restock,
    449   unsigned int http_status)
    450 {
    451   struct PostProductsState *pis;
    452 
    453   GNUNET_assert ((NULL == taxes) ||
    454                  json_is_array (taxes));
    455   GNUNET_assert ((NULL == description_i18n) ||
    456                  json_is_object (description_i18n));
    457   pis = GNUNET_new (struct PostProductsState);
    458   pis->merchant_url = merchant_url;
    459   pis->product_id = product_id;
    460   pis->http_status = http_status;
    461   pis->description = description;
    462   pis->description_i18n = description_i18n; /* ownership taken */
    463   pis->unit = unit;
    464   pis->unit_precision_level = default_precision_from_unit (unit);
    465   GNUNET_assert (GNUNET_OK ==
    466                  TALER_string_to_amount (price,
    467                                          &pis->price));
    468   pis->unit_prices = &pis->price;
    469   pis->unit_prices_len = 1;
    470   pis->owns_unit_prices = false;
    471   pis->use_unit_price_array = false;
    472   pis->image = GNUNET_strdup (image);
    473   pis->taxes = taxes; /* ownership taken */
    474   pis->total_stock = total_stock;
    475   pis->total_stock_frac = 0;
    476   pis->unit_allow_fraction = false;
    477   pis->use_fractional = false;
    478   pis->minimum_age = minimum_age;
    479   pis->address = address; /* ownership taken */
    480   pis->next_restock = next_restock;
    481   pis->num_cats = 0;
    482   pis->cats = NULL;
    483   post_products_update_unit_total_stock (pis);
    484   {
    485     struct TALER_TESTING_Command cmd = {
    486       .cls = pis,
    487       .label = label,
    488       .run = &post_products_run,
    489       .cleanup = &post_products_cleanup,
    490       .traits = &post_products_traits
    491     };
    492 
    493     return cmd;
    494   }
    495 }
    496 
    497 
    498 struct TALER_TESTING_Command
    499 TALER_TESTING_cmd_merchant_post_products3 (
    500   const char *label,
    501   const char *merchant_url,
    502   const char *product_id,
    503   const char *description,
    504   json_t *description_i18n,
    505   const char *unit,
    506   const char *price,
    507   const char *image,
    508   json_t *taxes,
    509   int64_t total_stock,
    510   uint32_t total_stock_frac,
    511   bool unit_allow_fraction,
    512   uint32_t minimum_age,
    513   json_t *address,
    514   struct GNUNET_TIME_Timestamp next_restock,
    515   unsigned int http_status)
    516 {
    517   struct TALER_TESTING_Command cmd;
    518 
    519   cmd = TALER_TESTING_cmd_merchant_post_products2 (label,
    520                                                    merchant_url,
    521                                                    product_id,
    522                                                    description,
    523                                                    description_i18n,
    524                                                    unit,
    525                                                    price,
    526                                                    image,
    527                                                    taxes,
    528                                                    total_stock,
    529                                                    minimum_age,
    530                                                    address,
    531                                                    next_restock,
    532                                                    http_status);
    533   {
    534     struct PostProductsState *pis = cmd.cls;
    535 
    536     pis->total_stock_frac = total_stock_frac;
    537     pis->unit_allow_fraction = unit_allow_fraction;
    538     pis->use_fractional = true;
    539     post_products_update_unit_total_stock (pis);
    540   }
    541   return cmd;
    542 }
    543 
    544 
    545 struct TALER_TESTING_Command
    546 TALER_TESTING_cmd_merchant_post_products_with_unit_prices (
    547   const char *label,
    548   const char *merchant_url,
    549   const char *product_id,
    550   const char *description,
    551   const char *unit,
    552   const char *const *unit_prices,
    553   size_t unit_prices_len,
    554   unsigned int http_status)
    555 {
    556   struct PostProductsState *pis;
    557 
    558   GNUNET_assert (0 < unit_prices_len);
    559   GNUNET_assert (NULL != unit_prices);
    560   pis = GNUNET_new (struct PostProductsState);
    561   pis->merchant_url = merchant_url;
    562   pis->product_id = product_id;
    563   pis->http_status = http_status;
    564   pis->description = description;
    565   pis->description_i18n = json_pack ("{s:s}", "en", description);
    566   pis->unit = unit;
    567   pis->unit_precision_level = default_precision_from_unit (unit);
    568   pis->unit_prices = GNUNET_new_array (unit_prices_len,
    569                                        struct TALER_Amount);
    570   pis->unit_prices_len = unit_prices_len;
    571   pis->owns_unit_prices = true;
    572   pis->use_unit_price_array = true;
    573   for (size_t i = 0; i < unit_prices_len; i++)
    574     GNUNET_assert (GNUNET_OK ==
    575                    TALER_string_to_amount (unit_prices[i],
    576                                            &pis->unit_prices[i]));
    577   pis->price = pis->unit_prices[0];
    578   pis->image = GNUNET_strdup ("");
    579   pis->taxes = json_array ();
    580   pis->total_stock = 4;
    581   pis->total_stock_frac = 0;
    582   pis->unit_allow_fraction = false;
    583   pis->use_fractional = false;
    584   pis->minimum_age = 0;
    585   pis->address = json_pack ("{s:s}", "street", "my street");
    586   pis->next_restock = GNUNET_TIME_UNIT_ZERO_TS;
    587   pis->num_cats = 0;
    588   pis->cats = NULL;
    589   post_products_update_unit_total_stock (pis);
    590   {
    591     struct TALER_TESTING_Command cmd = {
    592       .cls = pis,
    593       .label = label,
    594       .run = &post_products_run,
    595       .cleanup = &post_products_cleanup,
    596       .traits = &post_products_traits
    597     };
    598 
    599     return cmd;
    600   }
    601 }
    602 
    603 
    604 struct TALER_TESTING_Command
    605 TALER_TESTING_cmd_merchant_post_products (
    606   const char *label,
    607   const char *merchant_url,
    608   const char *product_id,
    609   const char *description,
    610   const char *price,
    611   unsigned int http_status)
    612 {
    613   return TALER_TESTING_cmd_merchant_post_products2 (
    614     label,
    615     merchant_url,
    616     product_id,
    617     description,
    618     json_pack ("{s:s}", "en", description),
    619     "test-unit",
    620     price,
    621     "",
    622     json_array (),
    623     4, /* total stock */
    624     0, /* minimum age */
    625     json_pack ("{s:s}", "street", "my street"),
    626     GNUNET_TIME_UNIT_ZERO_TS,
    627     http_status);
    628 }
    629 
    630 
    631 struct TALER_TESTING_Command
    632 TALER_TESTING_cmd_merchant_post_products_with_categories (
    633   const char *label,
    634   const char *merchant_url,
    635   const char *product_id,
    636   const char *description,
    637   const char *unit,
    638   const char *price,
    639   unsigned int num_cats,
    640   const uint64_t *cats,
    641   bool unit_allow_fraction,
    642   uint32_t unit_precision_level,
    643   unsigned int http_status)
    644 {
    645   struct TALER_TESTING_Command cmd;
    646 
    647   cmd = TALER_TESTING_cmd_merchant_post_products2 (
    648     label,
    649     merchant_url,
    650     product_id,
    651     description,
    652     json_pack ("{s:s}", "en", description),
    653     unit,
    654     price,
    655     "",
    656     json_array (),
    657     4, /* total stock */
    658     0, /* minimum age */
    659     json_pack ("{s:s}", "street", "my street"),
    660     GNUNET_TIME_UNIT_ZERO_TS,
    661     http_status);
    662   {
    663     struct PostProductsState *pis = cmd.cls;
    664 
    665     pis->num_cats = num_cats;
    666     if (0 < num_cats)
    667     {
    668       pis->cats = GNUNET_new_array (num_cats,
    669                                     uint64_t);
    670       for (unsigned int i = 0; i < num_cats; i++)
    671         pis->cats[i] = cats[i];
    672     }
    673     pis->unit_allow_fraction = unit_allow_fraction;
    674     pis->unit_precision_level = unit_precision_level;
    675     if (unit_allow_fraction)
    676       pis->use_fractional = true;
    677   }
    678   return cmd;
    679 }
    680 
    681 
    682 /* end of testing_api_cmd_post_products.c */