merchant

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

testing_api_cmd_post_products.c (18563B)


      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_testing_lib.h"
     28 #include <taler/taler-merchant/post-private-products.h>
     29 
     30 
     31 /**
     32  * State of a "POST /products" CMD.
     33  */
     34 struct PostProductsState
     35 {
     36 
     37   /**
     38    * Handle for a "POST /products" request.
     39    */
     40   struct TALER_MERCHANT_PostPrivateProductsHandle *iph;
     41 
     42   /**
     43    * The interpreter state.
     44    */
     45   struct TALER_TESTING_Interpreter *is;
     46 
     47   /**
     48    * Base URL of the merchant serving the request.
     49    */
     50   const char *merchant_url;
     51 
     52   /**
     53    * ID of the product to run POST for.
     54    */
     55   const char *product_id;
     56 
     57   /**
     58    * description of the product
     59    */
     60   const char *description;
     61 
     62   /**
     63    * Map from IETF BCP 47 language tags to localized descriptions
     64    */
     65   json_t *description_i18n;
     66 
     67   /**
     68    * unit in which the product is measured (liters, kilograms, packages, etc.)
     69    */
     70   const char *unit;
     71 
     72   /**
     73    * the price for one @a unit of the product
     74    */
     75   struct TALER_Amount price;
     76 
     77   /**
     78    * Array of unit prices (may point to @e price for single-entry usage).
     79    */
     80   struct TALER_Amount *unit_prices;
     81 
     82   /**
     83    * Number of entries in @e unit_prices.
     84    */
     85   size_t unit_prices_len;
     86 
     87   /**
     88    * True if @e unit_prices must be freed.
     89    */
     90   bool owns_unit_prices;
     91 
     92   /**
     93    * True if we should send an explicit unit_price array.
     94    */
     95   bool use_unit_price_array;
     96 
     97   /**
     98    * base64-encoded product image
     99    */
    100   char *image;
    101 
    102   /**
    103    * list of taxes paid by the merchant
    104    */
    105   json_t *taxes;
    106 
    107   /**
    108    * in @e units, -1 to indicate "infinite" (i.e. electronic books)
    109    */
    110   int64_t total_stock;
    111 
    112   /**
    113    * Fractional stock component when fractional quantities are enabled.
    114    */
    115   uint32_t total_stock_frac;
    116 
    117   /**
    118    * Fractional precision level associated with fractional quantities.
    119    */
    120   uint32_t unit_precision_level;
    121 
    122   /**
    123    * whether fractional quantities are allowed for this product.
    124    */
    125   bool unit_allow_fraction;
    126 
    127   /**
    128    * Cached string representation of the stock level.
    129    */
    130   char unit_total_stock[64];
    131 
    132   /**
    133    * set to true if we should use the extended fractional API.
    134    */
    135   bool use_fractional;
    136 
    137   /**
    138    * where the product is in stock
    139    */
    140   json_t *address;
    141 
    142   /**
    143    * Minimum age requirement to use for the product.
    144    */
    145   unsigned int minimum_age;
    146 
    147   /**
    148    * when the next restocking is expected to happen, 0 for unknown,
    149    */
    150   struct GNUNET_TIME_Timestamp next_restock;
    151 
    152   /**
    153    * Categories array length.
    154    */
    155   unsigned int num_cats;
    156 
    157   /**
    158    * Categories array.
    159    */
    160   uint64_t *cats;
    161 
    162   /**
    163    * Expected HTTP response code.
    164    */
    165   unsigned int http_status;
    166 
    167 };
    168 
    169 static void
    170 post_products_update_unit_total_stock (struct PostProductsState *pps)
    171 {
    172   if (-1 == pps->total_stock)
    173   {
    174     pps->total_stock = INT64_MAX;
    175     pps->total_stock_frac = INT32_MAX;
    176   }
    177   else if (! pps->unit_allow_fraction)
    178   {
    179     pps->total_stock_frac = 0;
    180   }
    181   TALER_MERCHANT_format_stock_string ((uint64_t) pps->total_stock,
    182                                       pps->total_stock_frac,
    183                                       pps->unit_total_stock,
    184                                       sizeof (pps->unit_total_stock));
    185 }
    186 
    187 
    188 static uint32_t
    189 default_precision_from_unit (const char *unit)
    190 {
    191   struct PrecisionRule
    192   {
    193     const char *unit;
    194     uint32_t precision;
    195   };
    196   static const struct PrecisionRule rules[] = {
    197     { "WeightUnitMg", 0 },
    198     { "SizeUnitMm", 0 },
    199     { "WeightUnitG", 1 },
    200     { "SizeUnitCm", 1 },
    201     { "SurfaceUnitMm2", 1 },
    202     { "VolumeUnitMm3", 1 },
    203     { "WeightUnitOunce", 2 },
    204     { "SizeUnitInch", 2 },
    205     { "SurfaceUnitCm2", 2 },
    206     { "VolumeUnitOunce", 2 },
    207     { "VolumeUnitInch3", 2 },
    208     { "TimeUnitHour", 2 },
    209     { "TimeUnitMonth", 2 },
    210     { "WeightUnitTon", 3 },
    211     { "WeightUnitKg", 3 },
    212     { "WeightUnitPound", 3 },
    213     { "SizeUnitM", 3 },
    214     { "SizeUnitDm", 3 },
    215     { "SizeUnitFoot", 3 },
    216     { "SurfaceUnitDm2", 3 },
    217     { "SurfaceUnitFoot2", 3 },
    218     { "VolumeUnitCm3", 3 },
    219     { "VolumeUnitLitre", 3 },
    220     { "VolumeUnitGallon", 3 },
    221     { "TimeUnitSecond", 3 },
    222     { "TimeUnitMinute", 3 },
    223     { "TimeUnitDay", 3 },
    224     { "TimeUnitWeek", 3 },
    225     { "SurfaceUnitM2", 4 },
    226     { "SurfaceUnitInch2", 4 },
    227     { "TimeUnitYear", 4 },
    228     { "VolumeUnitDm3", 5 },
    229     { "VolumeUnitFoot3", 5 },
    230     { "VolumeUnitM3", 6 }
    231   };
    232 
    233   const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
    234   if (NULL == unit)
    235     return 0;
    236 
    237   for (size_t i = 0; i<rules_len; i++)
    238     if (0 == strcmp (unit,
    239                      rules[i].unit))
    240       return rules[i].precision;
    241   return 0;
    242 }
    243 
    244 
    245 /**
    246  * Callback for a POST /products operation.
    247  *
    248  * @param cls closure for this function
    249  * @param hr response being processed
    250  */
    251 static void
    252 post_products_cb (void *cls,
    253                   const struct TALER_MERCHANT_PostPrivateProductsResponse *ppr)
    254 {
    255   struct PostProductsState *pis = cls;
    256 
    257   pis->iph = NULL;
    258   if (pis->http_status != ppr->hr.http_status)
    259   {
    260     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    261                 "Unexpected response code %u (%d) to command %s\n",
    262                 ppr->hr.http_status,
    263                 (int) ppr->hr.ec,
    264                 TALER_TESTING_interpreter_get_current_label (pis->is));
    265     TALER_TESTING_interpreter_fail (pis->is);
    266     return;
    267   }
    268   switch (ppr->hr.http_status)
    269   {
    270   case MHD_HTTP_NO_CONTENT:
    271     break;
    272   case MHD_HTTP_UNAUTHORIZED:
    273     break;
    274   case MHD_HTTP_FORBIDDEN:
    275     break;
    276   case MHD_HTTP_NOT_FOUND:
    277     break;
    278   case MHD_HTTP_CONFLICT:
    279     break;
    280   default:
    281     GNUNET_break (0);
    282     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    283                 "Unhandled HTTP status %u for POST /products.\n",
    284                 ppr->hr.http_status);
    285   }
    286   TALER_TESTING_interpreter_next (pis->is);
    287 }
    288 
    289 
    290 /**
    291  * Run the "POST /products" CMD.
    292  *
    293  *
    294  * @param cls closure.
    295  * @param cmd command being run now.
    296  * @param is interpreter state.
    297  */
    298 static void
    299 post_products_run (void *cls,
    300                    const struct TALER_TESTING_Command *cmd,
    301                    struct TALER_TESTING_Interpreter *is)
    302 {
    303   struct PostProductsState *pis = cls;
    304 
    305   pis->is = is;
    306   pis->iph = TALER_MERCHANT_post_private_products_create (
    307     TALER_TESTING_interpreter_get_context (is),
    308     pis->merchant_url,
    309     pis->product_id,
    310     pis->description,
    311     pis->unit,
    312     pis->unit_prices_len,
    313     pis->unit_prices);
    314   GNUNET_assert (GNUNET_OK ==
    315                  TALER_MERCHANT_post_private_products_set_options (
    316                    pis->iph,
    317                    TALER_MERCHANT_post_private_products_option_description_i18n (
    318                      pis->description_i18n),
    319                    TALER_MERCHANT_post_private_products_option_total_stock_val (
    320                      pis->total_stock),
    321                    TALER_MERCHANT_post_private_products_option_taxes (
    322                      pis->taxes),
    323                    TALER_MERCHANT_post_private_products_option_image (
    324                      pis->image),
    325                    TALER_MERCHANT_post_private_products_option_address (
    326                      pis->address),
    327                    TALER_MERCHANT_post_private_products_option_next_restock (
    328                      pis->next_restock),
    329                    TALER_MERCHANT_post_private_products_option_minimum_age (
    330                      pis->minimum_age),
    331                    TALER_MERCHANT_post_private_products_option_categories (
    332                      pis->num_cats,
    333                      pis->cats)));
    334   if (pis->use_fractional)
    335   {
    336     GNUNET_assert (
    337       GNUNET_OK ==
    338       TALER_MERCHANT_post_private_products_set_options (
    339         pis->iph,
    340         TALER_MERCHANT_post_private_products_option_total_stock_frac (
    341           pis->total_stock_frac),
    342         TALER_MERCHANT_post_private_products_option_unit_allow_fraction (
    343           pis->unit_allow_fraction),
    344         TALER_MERCHANT_post_private_products_option_unit_precision_level (
    345           pis->unit_precision_level)));
    346   }
    347   {
    348     enum TALER_ErrorCode ec;
    349 
    350     ec = TALER_MERCHANT_post_private_products_start (
    351       pis->iph,
    352       &post_products_cb,
    353       pis);
    354     GNUNET_assert (TALER_EC_NONE == ec);
    355   }
    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_post_private_products_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 */