merchant

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

testing_api_cmd_patch_product.c (16117B)


      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_patch_product.c
     21  * @brief command to test PATCH /product
     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 <taler/taler-merchant/patch-private-products-PRODUCT_ID.h>
     30 
     31 
     32 /**
     33  * State of a "PATCH /product" CMD.
     34  */
     35 struct PatchProductState
     36 {
     37 
     38   /**
     39    * Handle for a "GET product" request.
     40    */
     41   struct TALER_MERCHANT_PatchPrivateProductHandle *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 GET 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    * in @e units.
    140    */
    141   int64_t total_lost;
    142 
    143   /**
    144    * where the product is in stock
    145    */
    146   json_t *address;
    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    * Expected HTTP response code.
    155    */
    156   unsigned int http_status;
    157 
    158 };
    159 
    160 static void
    161 patch_product_update_unit_total_stock (struct PatchProductState *pps)
    162 {
    163   uint64_t stock;
    164   uint32_t frac;
    165 
    166   if (-1 == pps->total_stock)
    167   {
    168     stock = (uint64_t) INT64_MAX;
    169     frac = (uint32_t) INT32_MAX;
    170   }
    171   else
    172   {
    173     stock = (uint64_t) pps->total_stock;
    174     frac = pps->unit_allow_fraction ? pps->total_stock_frac : 0;
    175   }
    176   TALER_MERCHANT_format_stock_string (stock,
    177                                       frac,
    178                                       pps->unit_total_stock,
    179                                       sizeof (pps->unit_total_stock));
    180 }
    181 
    182 
    183 static uint32_t
    184 default_precision_from_unit (const char *unit)
    185 {
    186   struct PrecisionRule
    187   {
    188     const char *unit;
    189     uint32_t precision;
    190   };
    191   static const struct PrecisionRule rules[] = {
    192     { "WeightUnitMg", 0 },
    193     { "SizeUnitMm", 0 },
    194     { "WeightUnitG", 1 },
    195     { "SizeUnitCm", 1 },
    196     { "SurfaceUnitMm2", 1 },
    197     { "VolumeUnitMm3", 1 },
    198     { "WeightUnitOunce", 2 },
    199     { "SizeUnitInch", 2 },
    200     { "SurfaceUnitCm2", 2 },
    201     { "VolumeUnitOunce", 2 },
    202     { "VolumeUnitInch3", 2 },
    203     { "TimeUnitHour", 2 },
    204     { "TimeUnitMonth", 2 },
    205     { "WeightUnitTon", 3 },
    206     { "WeightUnitKg", 3 },
    207     { "WeightUnitPound", 3 },
    208     { "SizeUnitM", 3 },
    209     { "SizeUnitDm", 3 },
    210     { "SizeUnitFoot", 3 },
    211     { "SurfaceUnitDm2", 3 },
    212     { "SurfaceUnitFoot2", 3 },
    213     { "VolumeUnitCm3", 3 },
    214     { "VolumeUnitLitre", 3 },
    215     { "VolumeUnitGallon", 3 },
    216     { "TimeUnitSecond", 3 },
    217     { "TimeUnitMinute", 3 },
    218     { "TimeUnitDay", 3 },
    219     { "TimeUnitWeek", 3 },
    220     { "SurfaceUnitM2", 4 },
    221     { "SurfaceUnitInch2", 4 },
    222     { "TimeUnitYear", 4 },
    223     { "VolumeUnitDm3", 5 },
    224     { "VolumeUnitFoot3", 5 },
    225     { "VolumeUnitM3", 6 }
    226   };
    227 
    228   const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
    229   if (NULL == unit)
    230     return 0;
    231 
    232   for (size_t i = 0; i<rules_len; i++)
    233     if (0 == strcmp (unit,
    234                      rules[i].unit))
    235       return rules[i].precision;
    236   return 0;
    237 }
    238 
    239 
    240 /**
    241  * Callback for a PATCH /products/$ID operation.
    242  *
    243  * @param cls closure for this function
    244  * @param hr response being processed
    245  */
    246 static void
    247 patch_product_cb (void *cls,
    248                   const struct TALER_MERCHANT_PatchPrivateProductResponse *
    249                   result)
    250 {
    251   struct PatchProductState *pis = cls;
    252   const struct TALER_MERCHANT_HttpResponse *hr = &result->hr;
    253 
    254   pis->iph = NULL;
    255   if (pis->http_status != hr->http_status)
    256   {
    257     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    258                 "Unexpected response code %u (%d) to command %s\n",
    259                 hr->http_status,
    260                 (int) hr->ec,
    261                 TALER_TESTING_interpreter_get_current_label (pis->is));
    262     TALER_TESTING_interpreter_fail (pis->is);
    263     return;
    264   }
    265   switch (hr->http_status)
    266   {
    267   case MHD_HTTP_NO_CONTENT:
    268     break;
    269   case MHD_HTTP_UNAUTHORIZED:
    270     break;
    271   case MHD_HTTP_FORBIDDEN:
    272     break;
    273   case MHD_HTTP_NOT_FOUND:
    274     break;
    275   case MHD_HTTP_CONFLICT:
    276     break;
    277   default:
    278     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    279                 "Unhandled HTTP status %u for PATCH /products/ID.\n",
    280                 hr->http_status);
    281   }
    282   TALER_TESTING_interpreter_next (pis->is);
    283 }
    284 
    285 
    286 /**
    287  * Run the "PATCH /products/$ID" CMD.
    288  *
    289  *
    290  * @param cls closure.
    291  * @param cmd command being run now.
    292  * @param is interpreter state.
    293  */
    294 static void
    295 patch_product_run (void *cls,
    296                    const struct TALER_TESTING_Command *cmd,
    297                    struct TALER_TESTING_Interpreter *is)
    298 {
    299   struct PatchProductState *pis = cls;
    300 
    301   pis->is = is;
    302   pis->iph = TALER_MERCHANT_patch_private_product_create (
    303     TALER_TESTING_interpreter_get_context (is),
    304     pis->merchant_url,
    305     pis->product_id,
    306     pis->description,
    307     pis->unit,
    308     pis->unit_prices_len,
    309     pis->unit_prices);
    310   GNUNET_assert (
    311     GNUNET_OK ==
    312     TALER_MERCHANT_patch_private_product_set_options (
    313       pis->iph,
    314       TALER_MERCHANT_patch_private_product_option_description_i18n (pis->description_i18n),
    315       TALER_MERCHANT_patch_private_product_option_image (pis->image),
    316       TALER_MERCHANT_patch_private_product_option_taxes (pis->taxes),
    317       TALER_MERCHANT_patch_private_product_option_total_stock_val (pis->total_stock),
    318       TALER_MERCHANT_patch_private_product_option_total_lost (pis->total_lost),
    319       TALER_MERCHANT_patch_private_product_option_address (pis->address),
    320       TALER_MERCHANT_patch_private_product_option_next_restock (pis->next_restock)));
    321   GNUNET_assert (NULL != pis->iph);
    322   if (pis->use_fractional)
    323   {
    324     GNUNET_assert (
    325       GNUNET_OK ==
    326       TALER_MERCHANT_patch_private_product_set_options (
    327         pis->iph,
    328         TALER_MERCHANT_patch_private_product_option_total_stock_frac (
    329           pis->total_stock_frac),
    330         TALER_MERCHANT_patch_private_product_option_unit_allow_fraction (
    331           pis->unit_allow_fraction),
    332         TALER_MERCHANT_patch_private_product_option_unit_precision_level (
    333           pis->unit_precision_level)));
    334   }
    335   {
    336     enum TALER_ErrorCode ec;
    337 
    338     ec = TALER_MERCHANT_patch_private_product_start (
    339       pis->iph,
    340       &patch_product_cb,
    341       pis);
    342     GNUNET_assert (TALER_EC_NONE == ec);
    343   }
    344 }
    345 
    346 
    347 /**
    348  * Offers information from the PATCH /products CMD state to other
    349  * commands.
    350  *
    351  * @param cls closure
    352  * @param[out] ret result (could be anything)
    353  * @param trait name of the trait
    354  * @param index index number of the object to extract.
    355  * @return #GNUNET_OK on success
    356  */
    357 static enum GNUNET_GenericReturnValue
    358 patch_product_traits (void *cls,
    359                       const void **ret,
    360                       const char *trait,
    361                       unsigned int index)
    362 {
    363   struct PatchProductState *pps = cls;
    364   struct TALER_TESTING_Trait traits[] = {
    365     TALER_TESTING_make_trait_product_description (pps->description),
    366     TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
    367     TALER_TESTING_make_trait_product_unit (pps->unit),
    368     TALER_TESTING_make_trait_amount (&pps->price),
    369     TALER_TESTING_make_trait_product_image (pps->image),
    370     TALER_TESTING_make_trait_taxes (pps->taxes),
    371     TALER_TESTING_make_trait_product_stock (&pps->total_stock),
    372     TALER_TESTING_make_trait_product_unit_total_stock (
    373       pps->unit_total_stock),
    374     TALER_TESTING_make_trait_product_unit_precision_level (
    375       &pps->unit_precision_level),
    376     TALER_TESTING_make_trait_product_unit_allow_fraction (
    377       &pps->unit_allow_fraction),
    378     TALER_TESTING_make_trait_address (pps->address),
    379     TALER_TESTING_make_trait_timestamp (0,
    380                                         &pps->next_restock),
    381     TALER_TESTING_make_trait_product_id (pps->product_id),
    382     TALER_TESTING_trait_end (),
    383   };
    384 
    385   return TALER_TESTING_get_trait (traits,
    386                                   ret,
    387                                   trait,
    388                                   index);
    389 }
    390 
    391 
    392 /**
    393  * Free the state of a "GET product" CMD, and possibly
    394  * cancel a pending operation thereof.
    395  *
    396  * @param cls closure.
    397  * @param cmd command being run.
    398  */
    399 static void
    400 patch_product_cleanup (void *cls,
    401                        const struct TALER_TESTING_Command *cmd)
    402 {
    403   struct PatchProductState *pis = cls;
    404 
    405   if (NULL != pis->iph)
    406   {
    407     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    408                 "PATCH /products/$ID operation did not complete\n");
    409     TALER_MERCHANT_patch_private_product_cancel (pis->iph);
    410   }
    411   if (pis->owns_unit_prices)
    412     GNUNET_free (pis->unit_prices);
    413   json_decref (pis->description_i18n);
    414   GNUNET_free (pis->image);
    415   json_decref (pis->taxes);
    416   json_decref (pis->address);
    417   GNUNET_free (pis);
    418 }
    419 
    420 
    421 struct TALER_TESTING_Command
    422 TALER_TESTING_cmd_merchant_patch_product (
    423   const char *label,
    424   const char *merchant_url,
    425   const char *product_id,
    426   const char *description,
    427   json_t *description_i18n,
    428   const char *unit,
    429   const char *price,
    430   const char *image,
    431   json_t *taxes,
    432   int64_t total_stock,
    433   uint64_t total_lost,
    434   json_t *address,
    435   struct GNUNET_TIME_Timestamp next_restock,
    436   unsigned int http_status)
    437 {
    438   struct PatchProductState *pis;
    439 
    440   GNUNET_assert ( (NULL == taxes) ||
    441                   json_is_array (taxes));
    442   pis = GNUNET_new (struct PatchProductState);
    443   pis->merchant_url = merchant_url;
    444   pis->product_id = product_id;
    445   pis->http_status = http_status;
    446   pis->description = description;
    447   pis->description_i18n = description_i18n; /* ownership taken */
    448   pis->unit = unit;
    449   pis->unit_precision_level = default_precision_from_unit (unit);
    450   GNUNET_assert (GNUNET_OK ==
    451                  TALER_string_to_amount (price,
    452                                          &pis->price));
    453   pis->unit_prices = &pis->price;
    454   pis->unit_prices_len = 1;
    455   pis->owns_unit_prices = false;
    456   pis->use_unit_price_array = false;
    457   pis->image = GNUNET_strdup (image);
    458   pis->taxes = taxes; /* ownership taken */
    459   pis->total_stock = total_stock;
    460   pis->total_stock_frac = 0;
    461   pis->unit_allow_fraction = false;
    462   pis->use_fractional = false;
    463   pis->total_lost = total_lost;
    464   pis->address = address; /* ownership taken */
    465   pis->next_restock = next_restock;
    466   patch_product_update_unit_total_stock (pis);
    467   {
    468     struct TALER_TESTING_Command cmd = {
    469       .cls = pis,
    470       .label = label,
    471       .run = &patch_product_run,
    472       .cleanup = &patch_product_cleanup,
    473       .traits = &patch_product_traits
    474     };
    475 
    476     return cmd;
    477   }
    478 }
    479 
    480 
    481 struct TALER_TESTING_Command
    482 TALER_TESTING_cmd_merchant_patch_product2 (
    483   const char *label,
    484   const char *merchant_url,
    485   const char *product_id,
    486   const char *description,
    487   json_t *description_i18n,
    488   const char *unit,
    489   const char *price,
    490   const char *image,
    491   json_t *taxes,
    492   int64_t total_stock,
    493   uint32_t total_stock_frac,
    494   bool unit_allow_fraction,
    495   uint64_t total_lost,
    496   json_t *address,
    497   struct GNUNET_TIME_Timestamp next_restock,
    498   unsigned int http_status)
    499 {
    500   struct TALER_TESTING_Command cmd;
    501 
    502   cmd = TALER_TESTING_cmd_merchant_patch_product (label,
    503                                                   merchant_url,
    504                                                   product_id,
    505                                                   description,
    506                                                   description_i18n,
    507                                                   unit,
    508                                                   price,
    509                                                   image,
    510                                                   taxes,
    511                                                   total_stock,
    512                                                   total_lost,
    513                                                   address,
    514                                                   next_restock,
    515                                                   http_status);
    516   {
    517     struct PatchProductState *pps = cmd.cls;
    518 
    519     pps->total_stock_frac = total_stock_frac;
    520     pps->unit_allow_fraction = unit_allow_fraction;
    521     pps->use_fractional = true;
    522     patch_product_update_unit_total_stock (pps);
    523   }
    524   return cmd;
    525 }
    526 
    527 
    528 struct TALER_TESTING_Command
    529 TALER_TESTING_cmd_merchant_patch_product_with_unit_prices (
    530   const char *label,
    531   const char *merchant_url,
    532   const char *product_id,
    533   const char *description,
    534   const char *unit,
    535   const char *const *unit_prices,
    536   size_t unit_prices_len,
    537   unsigned int http_status)
    538 {
    539   struct PatchProductState *pis;
    540 
    541   GNUNET_assert (0 < unit_prices_len);
    542   GNUNET_assert (NULL != unit_prices);
    543   pis = GNUNET_new (struct PatchProductState);
    544   pis->merchant_url = merchant_url;
    545   pis->product_id = product_id;
    546   pis->http_status = http_status;
    547   pis->description = description;
    548   pis->description_i18n = json_pack ("{s:s}", "en", description);
    549   pis->unit = unit;
    550   pis->unit_precision_level = default_precision_from_unit (unit);
    551   pis->unit_prices = GNUNET_new_array (unit_prices_len,
    552                                        struct TALER_Amount);
    553   pis->unit_prices_len = unit_prices_len;
    554   pis->owns_unit_prices = true;
    555   pis->use_unit_price_array = true;
    556   for (size_t i = 0; i < unit_prices_len; i++)
    557     GNUNET_assert (GNUNET_OK ==
    558                    TALER_string_to_amount (unit_prices[i],
    559                                            &pis->unit_prices[i]));
    560   pis->price = pis->unit_prices[0];
    561   pis->image = GNUNET_strdup ("");
    562   pis->taxes = json_array ();
    563   pis->total_stock = 5;
    564   pis->total_stock_frac = 0;
    565   pis->unit_allow_fraction = true;
    566   pis->unit_precision_level = 1;
    567   pis->use_fractional = true;
    568   pis->total_lost = 0;
    569   pis->address = json_object ();
    570   pis->next_restock = GNUNET_TIME_UNIT_FOREVER_TS;
    571   patch_product_update_unit_total_stock (pis);
    572   {
    573     struct TALER_TESTING_Command cmd = {
    574       .cls = pis,
    575       .label = label,
    576       .run = &patch_product_run,
    577       .cleanup = &patch_product_cleanup,
    578       .traits = &patch_product_traits
    579     };
    580 
    581     return cmd;
    582   }
    583 }
    584 
    585 
    586 /* end of testing_api_cmd_patch_product.c */