merchant_api_patch-private-products-PRODUCT_ID.c (16068B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-2026 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_patch-private-products-PRODUCT_ID.c 21 * @brief Implementation of the PATCH /private/products/$PRODUCT_ID request 22 * @author Christian Grothoff 23 */ 24 #include "taler/platform.h" 25 #include <curl/curl.h> 26 #include <jansson.h> 27 #include <microhttpd.h> /* just for HTTP status codes */ 28 #include <gnunet/gnunet_util_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include <taler/taler-merchant/patch-private-products-PRODUCT_ID.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 PATCH /private/products/$PRODUCT_ID operation. 39 */ 40 struct TALER_MERCHANT_PatchPrivateProductHandle 41 { 42 /** 43 * Base URL of the merchant backend. 44 */ 45 char *base_url; 46 47 /** 48 * The full URL for this request. 49 */ 50 char *url; 51 52 /** 53 * Handle for the request. 54 */ 55 struct GNUNET_CURL_Job *job; 56 57 /** 58 * Function to call with the result. 59 */ 60 TALER_MERCHANT_PatchPrivateProductCallback cb; 61 62 /** 63 * Closure for @a cb. 64 */ 65 TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_RESULT_CLOSURE *cb_cls; 66 67 /** 68 * Reference to the execution context. 69 */ 70 struct GNUNET_CURL_Context *ctx; 71 72 /** 73 * Minor context that holds body and headers. 74 */ 75 struct TALER_CURL_PostContext post_ctx; 76 77 /** 78 * Identifier of the product to update. 79 */ 80 char *product_id; 81 82 /** 83 * New human-readable description. 84 */ 85 char *description; 86 87 /** 88 * Internationalized descriptions (JSON). 89 */ 90 json_t *description_i18n; 91 92 /** 93 * Unit of measurement. 94 */ 95 char *unit; 96 97 /** 98 * New base64-encoded image. 99 */ 100 char *image; 101 102 /** 103 * Explicit product name (if different from description). 104 */ 105 char *product_name; 106 107 /** 108 * Optional category IDs. 109 */ 110 uint64_t *cats; 111 112 /** 113 * Product group ID. 114 */ 115 uint64_t product_group_id; 116 117 /** 118 * Money pot ID. 119 */ 120 uint64_t money_pot_id; 121 122 /** 123 * Whether money_pot_id has been set. 124 */ 125 bool have_money_pot_id; 126 127 /** 128 * Whether the price is net (before tax). 129 */ 130 bool price_is_net; 131 132 /** 133 * Whether price_is_net has been set. 134 */ 135 bool have_price_is_net; 136 137 /** 138 * Whether product_group_id has been set. 139 */ 140 bool have_product_group_id; 141 142 /** 143 * Number of category IDs. 144 */ 145 unsigned int num_cats; 146 147 /** 148 * New tax information (JSON array). 149 */ 150 json_t *taxes; 151 152 /** 153 * New total stock (-1 for unlimited). 154 */ 155 int64_t total_stock; 156 157 /** 158 * Total units lost/expired. 159 */ 160 uint64_t total_lost; 161 162 /** 163 * Storage location (JSON). 164 */ 165 json_t *address; 166 167 /** 168 * Expected restock time. 169 */ 170 struct GNUNET_TIME_Timestamp next_restock; 171 172 /** 173 * Array of unit prices. 174 */ 175 struct TALER_Amount *unit_prices; 176 177 /** 178 * Optional minimum age requirement. 179 */ 180 uint32_t minimum_age; 181 182 /** 183 * Whether minimum_age has been set. 184 */ 185 bool have_minimum_age; 186 187 /** 188 * Number of prices in @e unit_prices. 189 */ 190 size_t unit_price_len; 191 192 /** 193 * Fractional part of total stock. 194 */ 195 uint32_t total_stock_frac; 196 197 /** 198 * Whether fractional quantities are allowed. 199 */ 200 bool unit_allow_fraction; 201 202 /** 203 * Whether @e next_restock was explicitly set. 204 */ 205 bool have_next_restock; 206 207 /** 208 * Whether @e unit_allow_fraction was explicitly set. 209 */ 210 bool have_unit_allow_fraction; 211 212 /** 213 * Precision level for fractions. 214 */ 215 uint32_t unit_precision_level; 216 217 /** 218 * Whether @e unit_precision_level was explicitly set. 219 */ 220 bool have_unit_precision_level; 221 }; 222 223 224 /** 225 * Function called when we're done processing the 226 * HTTP PATCH /private/products/$PRODUCT_ID request. 227 * 228 * @param cls the `struct TALER_MERCHANT_PatchPrivateProductHandle` 229 * @param response_code HTTP response code, 0 on error 230 * @param response response body, NULL if not in JSON 231 */ 232 static void 233 handle_patch_product_finished (void *cls, 234 long response_code, 235 const void *response) 236 { 237 struct TALER_MERCHANT_PatchPrivateProductHandle *pph = cls; 238 const json_t *json = response; 239 struct TALER_MERCHANT_PatchPrivateProductResponse ppr = { 240 .hr.http_status = (unsigned int) response_code, 241 .hr.reply = json 242 }; 243 244 pph->job = NULL; 245 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 246 "PATCH /private/products/$PRODUCT_ID completed with response code %u\n", 247 (unsigned int) response_code); 248 switch (response_code) 249 { 250 case 0: 251 ppr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 252 break; 253 case MHD_HTTP_NO_CONTENT: 254 break; 255 case MHD_HTTP_BAD_REQUEST: 256 ppr.hr.ec = TALER_JSON_get_error_code (json); 257 ppr.hr.hint = TALER_JSON_get_error_hint (json); 258 GNUNET_break_op (0); 259 /* This should never happen, either us 260 * or the merchant is buggy (or API version conflict); 261 * just pass JSON reply to the application */ 262 break; 263 case MHD_HTTP_UNAUTHORIZED: 264 ppr.hr.ec = TALER_JSON_get_error_code (json); 265 ppr.hr.hint = TALER_JSON_get_error_hint (json); 266 /* Nothing really to verify, merchant says we need to authenticate. */ 267 break; 268 case MHD_HTTP_FORBIDDEN: 269 ppr.hr.ec = TALER_JSON_get_error_code (json); 270 ppr.hr.hint = TALER_JSON_get_error_hint (json); 271 break; 272 case MHD_HTTP_NOT_FOUND: 273 ppr.hr.ec = TALER_JSON_get_error_code (json); 274 ppr.hr.hint = TALER_JSON_get_error_hint (json); 275 break; 276 case MHD_HTTP_CONFLICT: 277 ppr.hr.ec = TALER_JSON_get_error_code (json); 278 ppr.hr.hint = TALER_JSON_get_error_hint (json); 279 break; 280 case MHD_HTTP_INTERNAL_SERVER_ERROR: 281 ppr.hr.ec = TALER_JSON_get_error_code (json); 282 ppr.hr.hint = TALER_JSON_get_error_hint (json); 283 /* Server had an internal issue; we should retry, 284 but this API leaves this to the application */ 285 break; 286 default: 287 TALER_MERCHANT_parse_error_details_ (json, 288 response_code, 289 &ppr.hr); 290 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 291 "Unexpected response code %u/%d\n", 292 (unsigned int) response_code, 293 (int) ppr.hr.ec); 294 GNUNET_break_op (0); 295 break; 296 } 297 pph->cb (pph->cb_cls, 298 &ppr); 299 TALER_MERCHANT_patch_private_product_cancel (pph); 300 } 301 302 303 struct TALER_MERCHANT_PatchPrivateProductHandle * 304 TALER_MERCHANT_patch_private_product_create ( 305 struct GNUNET_CURL_Context *ctx, 306 const char *url, 307 const char *product_id, 308 const char *description, 309 const char *unit, 310 unsigned int num_prices, 311 const struct TALER_Amount prices[static num_prices]) 312 { 313 struct TALER_MERCHANT_PatchPrivateProductHandle *pph; 314 315 pph = GNUNET_new (struct TALER_MERCHANT_PatchPrivateProductHandle); 316 pph->ctx = ctx; 317 pph->base_url = GNUNET_strdup (url); 318 pph->product_id = GNUNET_strdup (product_id); 319 pph->description = GNUNET_strdup (description); 320 pph->unit = GNUNET_strdup (unit); 321 pph->unit_price_len = num_prices; 322 pph->unit_prices = GNUNET_new_array (num_prices, 323 struct TALER_Amount); 324 memcpy (pph->unit_prices, 325 prices, 326 num_prices * sizeof (struct TALER_Amount)); 327 return pph; 328 } 329 330 331 enum GNUNET_GenericReturnValue 332 TALER_MERCHANT_patch_private_product_set_options_ ( 333 struct TALER_MERCHANT_PatchPrivateProductHandle *pph, 334 unsigned int num_options, 335 const struct TALER_MERCHANT_PatchPrivateProductOptionValue *options) 336 { 337 for (unsigned int i = 0; i < num_options; i++) 338 { 339 switch (options[i].option) 340 { 341 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_END: 342 return GNUNET_OK; 343 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_STOCK_FRAC: 344 pph->total_stock_frac = options[i].details.total_stock.frac; 345 continue; 346 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_STOCK_VAL: 347 pph->total_stock = options[i].details.total_stock.val; 348 continue; 349 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_STOCK: 350 pph->total_stock = options[i].details.total_stock.val; 351 pph->total_stock_frac = options[i].details.total_stock.frac; 352 continue; 353 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_LOST: 354 pph->total_lost = options[i].details.total_lost; 355 continue; 356 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_UNIT_ALLOW_FRACTION: 357 pph->unit_allow_fraction = options[i].details.unit_allow_fraction; 358 pph->have_unit_allow_fraction = true; 359 continue; 360 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_UNIT_PRECISION_LEVEL: 361 pph->unit_precision_level = options[i].details.unit_precision_level; 362 pph->have_unit_precision_level = true; 363 continue; 364 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_DESCRIPTION_I18N: 365 json_decref (pph->description_i18n); 366 pph->description_i18n = json_incref ( 367 (json_t *) options[i].details.description_i18n); 368 continue; 369 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TAXES: 370 json_decref (pph->taxes); 371 pph->taxes = json_incref ((json_t *) options[i].details.taxes); 372 continue; 373 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_ADDRESS: 374 json_decref (pph->address); 375 pph->address = json_incref ((json_t *) options[i].details.address); 376 continue; 377 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_NEXT_RESTOCK: 378 pph->next_restock = options[i].details.next_restock; 379 pph->have_next_restock = true; 380 continue; 381 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_MINIMUM_AGE: 382 pph->minimum_age = options[i].details.minimum_age; 383 pph->have_minimum_age = true; 384 continue; 385 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_CATEGORIES: 386 pph->num_cats = options[i].details.categories.num; 387 GNUNET_free (pph->cats); 388 pph->cats = GNUNET_new_array (pph->num_cats, 389 uint64_t); 390 memcpy (pph->cats, 391 options[i].details.categories.cats, 392 pph->num_cats * sizeof (uint64_t)); 393 continue; 394 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_PRODUCT_NAME: 395 GNUNET_free (pph->product_name); 396 pph->product_name = GNUNET_strdup (options[i].details.product_name); 397 continue; 398 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_IMAGE: 399 GNUNET_free (pph->image); 400 pph->image = GNUNET_strdup (options[i].details.image); 401 continue; 402 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_PRODUCT_GROUP_ID: 403 pph->product_group_id = options[i].details.product_group_id; 404 pph->have_product_group_id = true; 405 continue; 406 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_MONEY_POT_ID: 407 pph->money_pot_id = options[i].details.money_pot_id; 408 pph->have_money_pot_id = true; 409 continue; 410 case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_PRICE_IS_NET: 411 pph->price_is_net = options[i].details.price_is_net; 412 pph->have_price_is_net = true; 413 continue; 414 } 415 GNUNET_break (0); 416 return GNUNET_SYSERR; 417 } 418 return GNUNET_OK; 419 } 420 421 422 enum TALER_ErrorCode 423 TALER_MERCHANT_patch_private_product_start ( 424 struct TALER_MERCHANT_PatchPrivateProductHandle *pph, 425 TALER_MERCHANT_PatchPrivateProductCallback cb, 426 TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_RESULT_CLOSURE *cb_cls) 427 { 428 json_t *req_obj; 429 CURL *eh; 430 char unit_total_stock_buf[64]; 431 432 pph->cb = cb; 433 pph->cb_cls = cb_cls; 434 { 435 char *path; 436 437 GNUNET_asprintf (&path, 438 "private/products/%s", 439 pph->product_id); 440 pph->url = TALER_url_join (pph->base_url, 441 path, 442 NULL); 443 GNUNET_free (path); 444 } 445 if (NULL == pph->url) 446 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 447 TALER_MERCHANT_format_stock_string (pph->total_stock, 448 pph->total_stock_frac, 449 unit_total_stock_buf, 450 sizeof (unit_total_stock_buf)); 451 452 req_obj = GNUNET_JSON_PACK ( 453 GNUNET_JSON_pack_string ("product_name", 454 pph->description), 455 GNUNET_JSON_pack_string ("description", 456 pph->description), 457 GNUNET_JSON_pack_object_incref ("description_i18n", 458 pph->description_i18n), 459 GNUNET_JSON_pack_string ("unit", 460 pph->unit), 461 TALER_JSON_pack_amount_array ("unit_price", 462 pph->unit_price_len, 463 pph->unit_prices), 464 GNUNET_JSON_pack_string ("image", 465 pph->image), 466 GNUNET_JSON_pack_array_incref ("taxes", 467 pph->taxes), 468 GNUNET_JSON_pack_string ("unit_total_stock", 469 unit_total_stock_buf), 470 GNUNET_JSON_pack_uint64 ("total_lost", 471 pph->total_lost), 472 GNUNET_JSON_pack_object_incref ("address", 473 pph->address), 474 GNUNET_JSON_pack_timestamp ("next_restock", 475 pph->next_restock)); 476 if (pph->have_unit_allow_fraction && 477 pph->unit_allow_fraction) 478 { 479 GNUNET_assert (0 == 480 json_object_set_new (req_obj, 481 "unit_allow_fraction", 482 json_boolean ( 483 pph->unit_allow_fraction))); 484 if (pph->have_unit_precision_level) 485 { 486 GNUNET_assert (0 == 487 json_object_set_new (req_obj, 488 "unit_precision_level", 489 json_integer ( 490 pph->unit_precision_level))); 491 } 492 } 493 eh = TALER_MERCHANT_curl_easy_get_ (pph->url); 494 if ( (NULL == eh) || 495 (GNUNET_OK != 496 TALER_curl_easy_post (&pph->post_ctx, 497 eh, 498 req_obj)) ) 499 { 500 GNUNET_break (0); 501 json_decref (req_obj); 502 if (NULL != eh) 503 curl_easy_cleanup (eh); 504 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 505 } 506 json_decref (req_obj); 507 GNUNET_assert (CURLE_OK == 508 curl_easy_setopt (eh, 509 CURLOPT_CUSTOMREQUEST, 510 MHD_HTTP_METHOD_PATCH)); 511 pph->job = GNUNET_CURL_job_add2 (pph->ctx, 512 eh, 513 pph->post_ctx.headers, 514 &handle_patch_product_finished, 515 pph); 516 if (NULL == pph->job) 517 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 518 return TALER_EC_NONE; 519 } 520 521 522 void 523 TALER_MERCHANT_patch_private_product_cancel ( 524 struct TALER_MERCHANT_PatchPrivateProductHandle *pph) 525 { 526 if (NULL != pph->job) 527 { 528 GNUNET_CURL_job_cancel (pph->job); 529 pph->job = NULL; 530 } 531 TALER_curl_easy_post_finished (&pph->post_ctx); 532 json_decref (pph->description_i18n); 533 json_decref (pph->taxes); 534 json_decref (pph->address); 535 GNUNET_free (pph->cats); 536 GNUNET_free (pph->unit_prices); 537 GNUNET_free (pph->url); 538 GNUNET_free (pph->base_url); 539 GNUNET_free (pph->product_id); 540 GNUNET_free (pph->description); 541 GNUNET_free (pph->unit); 542 GNUNET_free (pph->image); 543 GNUNET_free (pph->product_name); 544 GNUNET_free (pph); 545 } 546 547 548 /* end of merchant_api_patch-private-products-PRODUCT_ID.c */