merchant_api_patch-private-products-PRODUCT_ID.c (17348B)
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 src/lib/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 "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/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 json_t *categories; 430 CURL *eh; 431 char unit_total_stock_buf[64]; 432 433 pph->cb = cb; 434 pph->cb_cls = cb_cls; 435 { 436 char *path; 437 438 GNUNET_asprintf (&path, 439 "private/products/%s", 440 pph->product_id); 441 pph->url = TALER_url_join (pph->base_url, 442 path, 443 NULL); 444 GNUNET_free (path); 445 } 446 if (NULL == pph->url) 447 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 448 TALER_MERCHANT_format_stock_string (pph->total_stock, 449 pph->total_stock_frac, 450 unit_total_stock_buf, 451 sizeof (unit_total_stock_buf)); 452 453 if (0 == pph->num_cats) 454 { 455 categories = NULL; 456 } 457 else 458 { 459 categories = json_array (); 460 GNUNET_assert (NULL != categories); 461 for (unsigned int i = 0; i < pph->num_cats; i++) 462 GNUNET_assert (0 == 463 json_array_append_new (categories, 464 json_integer (pph->cats[i]))); 465 } 466 467 req_obj = GNUNET_JSON_PACK ( 468 GNUNET_JSON_pack_string ("product_name", 469 (NULL == pph->product_name) 470 ? pph->description 471 : pph->product_name), 472 GNUNET_JSON_pack_string ("description", 473 pph->description), 474 GNUNET_JSON_pack_allow_null ( 475 GNUNET_JSON_pack_object_incref ("description_i18n", 476 pph->description_i18n)), 477 GNUNET_JSON_pack_allow_null ( 478 GNUNET_JSON_pack_array_steal ("categories", 479 categories)), 480 GNUNET_JSON_pack_string ("unit", 481 pph->unit), 482 TALER_JSON_pack_amount_array ("unit_price", 483 pph->unit_price_len, 484 pph->unit_prices), 485 GNUNET_JSON_pack_allow_null ( 486 GNUNET_JSON_pack_string ("image", 487 pph->image)), 488 GNUNET_JSON_pack_allow_null ( 489 GNUNET_JSON_pack_array_incref ("taxes", 490 pph->taxes)), 491 GNUNET_JSON_pack_string ("unit_total_stock", 492 unit_total_stock_buf), 493 GNUNET_JSON_pack_uint64 ("total_lost", 494 pph->total_lost), 495 GNUNET_JSON_pack_allow_null ( 496 GNUNET_JSON_pack_object_incref ("address", 497 pph->address)), 498 GNUNET_JSON_pack_conditional ( 499 pph->have_minimum_age, 500 GNUNET_JSON_pack_uint64 ("minimum_age", 501 pph->minimum_age)), 502 GNUNET_JSON_pack_conditional ( 503 pph->have_product_group_id, 504 GNUNET_JSON_pack_uint64 ("product_group_id", 505 pph->product_group_id)), 506 GNUNET_JSON_pack_conditional ( 507 pph->have_money_pot_id, 508 GNUNET_JSON_pack_uint64 ("money_pot_id", 509 pph->money_pot_id)), 510 GNUNET_JSON_pack_conditional ( 511 pph->have_unit_allow_fraction && 512 pph->unit_allow_fraction, 513 GNUNET_JSON_pack_bool ("unit_allow_fraction", 514 pph->unit_allow_fraction)), 515 GNUNET_JSON_pack_conditional ( 516 pph->have_unit_allow_fraction && 517 pph->unit_allow_fraction && 518 pph->have_unit_precision_level, 519 GNUNET_JSON_pack_uint64 ("unit_precision_level", 520 pph->unit_precision_level)), 521 GNUNET_JSON_pack_conditional ( 522 pph->have_price_is_net, 523 GNUNET_JSON_pack_bool ("price_is_net", 524 pph->price_is_net)), 525 GNUNET_JSON_pack_allow_null ( 526 GNUNET_JSON_pack_timestamp ("next_restock", 527 pph->next_restock))); 528 eh = TALER_MERCHANT_curl_easy_get_ (pph->url); 529 if ( (NULL == eh) || 530 (GNUNET_OK != 531 TALER_curl_easy_post (&pph->post_ctx, 532 eh, 533 req_obj)) ) 534 { 535 GNUNET_break (0); 536 json_decref (req_obj); 537 if (NULL != eh) 538 curl_easy_cleanup (eh); 539 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 540 } 541 json_decref (req_obj); 542 GNUNET_assert (CURLE_OK == 543 curl_easy_setopt (eh, 544 CURLOPT_CUSTOMREQUEST, 545 MHD_HTTP_METHOD_PATCH)); 546 pph->job = GNUNET_CURL_job_add2 (pph->ctx, 547 eh, 548 pph->post_ctx.headers, 549 &handle_patch_product_finished, 550 pph); 551 if (NULL == pph->job) 552 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 553 return TALER_EC_NONE; 554 } 555 556 557 void 558 TALER_MERCHANT_patch_private_product_cancel ( 559 struct TALER_MERCHANT_PatchPrivateProductHandle *pph) 560 { 561 if (NULL != pph->job) 562 { 563 GNUNET_CURL_job_cancel (pph->job); 564 pph->job = NULL; 565 } 566 TALER_curl_easy_post_finished (&pph->post_ctx); 567 json_decref (pph->description_i18n); 568 json_decref (pph->taxes); 569 json_decref (pph->address); 570 GNUNET_free (pph->cats); 571 GNUNET_free (pph->unit_prices); 572 GNUNET_free (pph->url); 573 GNUNET_free (pph->base_url); 574 GNUNET_free (pph->product_id); 575 GNUNET_free (pph->description); 576 GNUNET_free (pph->unit); 577 GNUNET_free (pph->image); 578 GNUNET_free (pph->product_name); 579 GNUNET_free (pph); 580 } 581 582 583 /* end of merchant_api_patch-private-products-PRODUCT_ID.c */