merchant_api_post-private-products.c (17238B)
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_post-private-products-new.c 21 * @brief Implementation of the POST /private/products 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/post-private-products.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 POST /private/products operation. 39 */ 40 struct TALER_MERCHANT_PostPrivateProductsHandle 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_PostPrivateProductsCallback cb; 61 62 /** 63 * Closure for @a cb. 64 */ 65 TALER_MERCHANT_POST_PRIVATE_PRODUCTS_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 * Product identifier. 79 */ 80 char *product_id; 81 82 /** 83 * Human-readable description. 84 */ 85 char *description; 86 87 /** 88 * Unit of measurement. 89 */ 90 char *unit; 91 92 /** 93 * Product image (base64-encoded or empty), set via option. 94 */ 95 char *image; 96 97 /** 98 * Explicit product name (if different from description). 99 */ 100 char *product_name; 101 102 /** 103 * Total stock (-1 for unlimited). 104 */ 105 int64_t total_stock; 106 107 /** 108 * Product group ID. 109 */ 110 uint64_t product_group_id; 111 112 /** 113 * Whether product_group_id has been set. 114 */ 115 bool have_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 * Optional internationalized descriptions (JSON). 139 */ 140 json_t *description_i18n; 141 142 /** 143 * Optional tax information (JSON array). 144 */ 145 json_t *taxes; 146 147 /** 148 * Optional storage location (JSON). 149 */ 150 json_t *address; 151 152 /** 153 * Optional expected restock time. 154 */ 155 struct GNUNET_TIME_Timestamp next_restock; 156 157 /** 158 * Whether next_restock has been set. 159 */ 160 bool have_next_restock; 161 162 /** 163 * Optional minimum age requirement. 164 */ 165 uint32_t minimum_age; 166 167 /** 168 * Whether minimum_age has been set. 169 */ 170 bool have_minimum_age; 171 172 /** 173 * Optional category IDs. 174 */ 175 uint64_t *cats; 176 177 /** 178 * Number of category IDs. 179 */ 180 unsigned int num_cats; 181 182 /** 183 * Array of unit prices. 184 */ 185 struct TALER_Amount *unit_prices; 186 187 /** 188 * Length of @e unit_prices array. 189 */ 190 size_t unit_prices_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 unit_allow_fraction has been set. 204 */ 205 bool have_unit_allow_fraction; 206 207 /** 208 * Precision level for fractions. 209 */ 210 uint32_t unit_precision_level; 211 212 /** 213 * Whether unit_precision_level has been set. 214 */ 215 bool have_unit_precision_level; 216 }; 217 218 219 /** 220 * Function called when we're done processing the 221 * HTTP POST /private/products request. 222 * 223 * @param cls the `struct TALER_MERCHANT_PostPrivateProductsHandle` 224 * @param response_code HTTP response code, 0 on error 225 * @param response response body, NULL if not in JSON 226 */ 227 static void 228 handle_post_products_finished (void *cls, 229 long response_code, 230 const void *response) 231 { 232 struct TALER_MERCHANT_PostPrivateProductsHandle *ppph = cls; 233 const json_t *json = response; 234 struct TALER_MERCHANT_PostPrivateProductsResponse ppr = { 235 .hr.http_status = (unsigned int) response_code, 236 .hr.reply = json 237 }; 238 239 ppph->job = NULL; 240 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 241 "POST /private/products completed with response code %u\n", 242 (unsigned int) response_code); 243 switch (response_code) 244 { 245 case 0: 246 ppr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 247 break; 248 case MHD_HTTP_NO_CONTENT: 249 break; 250 case MHD_HTTP_BAD_REQUEST: 251 ppr.hr.ec = TALER_JSON_get_error_code (json); 252 ppr.hr.hint = TALER_JSON_get_error_hint (json); 253 break; 254 case MHD_HTTP_UNAUTHORIZED: 255 ppr.hr.ec = TALER_JSON_get_error_code (json); 256 ppr.hr.hint = TALER_JSON_get_error_hint (json); 257 break; 258 case MHD_HTTP_FORBIDDEN: 259 ppr.hr.ec = TALER_JSON_get_error_code (json); 260 ppr.hr.hint = TALER_JSON_get_error_hint (json); 261 break; 262 case MHD_HTTP_NOT_FOUND: 263 ppr.hr.ec = TALER_JSON_get_error_code (json); 264 ppr.hr.hint = TALER_JSON_get_error_hint (json); 265 break; 266 case MHD_HTTP_CONFLICT: 267 ppr.hr.ec = TALER_JSON_get_error_code (json); 268 ppr.hr.hint = TALER_JSON_get_error_hint (json); 269 break; 270 case MHD_HTTP_INTERNAL_SERVER_ERROR: 271 ppr.hr.ec = TALER_JSON_get_error_code (json); 272 ppr.hr.hint = TALER_JSON_get_error_hint (json); 273 break; 274 default: 275 TALER_MERCHANT_parse_error_details_ (json, 276 response_code, 277 &ppr.hr); 278 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 279 "Unexpected response code %u/%d\n", 280 (unsigned int) response_code, 281 (int) ppr.hr.ec); 282 GNUNET_break_op (0); 283 break; 284 } 285 ppph->cb (ppph->cb_cls, 286 &ppr); 287 TALER_MERCHANT_post_private_products_cancel (ppph); 288 } 289 290 291 struct TALER_MERCHANT_PostPrivateProductsHandle * 292 TALER_MERCHANT_post_private_products_create ( 293 struct GNUNET_CURL_Context *ctx, 294 const char *url, 295 const char *product_id, 296 const char *description, 297 const char *unit, 298 unsigned int num_prices, 299 const struct TALER_Amount prices[static num_prices]) 300 { 301 struct TALER_MERCHANT_PostPrivateProductsHandle *ppph; 302 303 ppph = GNUNET_new (struct TALER_MERCHANT_PostPrivateProductsHandle); 304 ppph->ctx = ctx; 305 ppph->base_url = GNUNET_strdup (url); 306 ppph->product_id = GNUNET_strdup (product_id); 307 ppph->description = GNUNET_strdup (description); 308 ppph->unit = GNUNET_strdup (unit); 309 ppph->unit_prices_len = num_prices; 310 ppph->unit_prices = GNUNET_new_array (num_prices, 311 struct TALER_Amount); 312 memcpy (ppph->unit_prices, 313 prices, 314 num_prices * sizeof (struct TALER_Amount)); 315 return ppph; 316 } 317 318 319 enum GNUNET_GenericReturnValue 320 TALER_MERCHANT_post_private_products_set_options_ ( 321 struct TALER_MERCHANT_PostPrivateProductsHandle *ppph, 322 unsigned int num_options, 323 const struct TALER_MERCHANT_PostPrivateProductsOptionValue *options) 324 { 325 for (unsigned int i = 0; i < num_options; i++) 326 { 327 switch (options[i].option) 328 { 329 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_END: 330 return GNUNET_OK; 331 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_DESCRIPTION_I18N: 332 json_decref (ppph->description_i18n); 333 ppph->description_i18n = json_incref ((json_t *) options[i].details. 334 description_i18n); 335 continue; 336 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TAXES: 337 json_decref (ppph->taxes); 338 ppph->taxes = json_incref ((json_t *) options[i].details.taxes); 339 continue; 340 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_ADDRESS: 341 json_decref (ppph->address); 342 ppph->address = json_incref ((json_t *) options[i].details.address); 343 continue; 344 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_NEXT_RESTOCK: 345 ppph->next_restock = options[i].details.next_restock; 346 ppph->have_next_restock = true; 347 continue; 348 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_MINIMUM_AGE: 349 ppph->minimum_age = options[i].details.minimum_age; 350 ppph->have_minimum_age = true; 351 continue; 352 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_CATEGORIES: 353 ppph->num_cats = options[i].details.categories.num; 354 GNUNET_free (ppph->cats); 355 ppph->cats = GNUNET_new_array (ppph->num_cats, 356 uint64_t); 357 memcpy (ppph->cats, 358 options[i].details.categories.cats, 359 ppph->num_cats * sizeof (uint64_t)); 360 continue; 361 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TOTAL_STOCK_FRAC: 362 ppph->total_stock_frac = options[i].details.total_stock.frac; 363 continue; 364 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TOTAL_STOCK_VAL: 365 ppph->total_stock = options[i].details.total_stock.val; 366 continue; 367 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TOTAL_STOCK: 368 ppph->total_stock_frac = options[i].details.total_stock.frac; 369 ppph->total_stock = options[i].details.total_stock.val; 370 continue; 371 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_UNIT_ALLOW_FRACTION: 372 ppph->unit_allow_fraction = options[i].details.unit_allow_fraction; 373 ppph->have_unit_allow_fraction = true; 374 continue; 375 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_UNIT_PRECISION_LEVEL: 376 ppph->unit_precision_level = options[i].details.unit_precision_level; 377 ppph->have_unit_precision_level = true; 378 continue; 379 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_PRODUCT_NAME: 380 GNUNET_free (ppph->product_name); 381 ppph->product_name = GNUNET_strdup (options[i].details.product_name); 382 continue; 383 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_IMAGE: 384 GNUNET_free (ppph->image); 385 ppph->image = GNUNET_strdup (options[i].details.image); 386 continue; 387 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_PRODUCT_GROUP_ID: 388 ppph->product_group_id = options[i].details.product_group_id; 389 ppph->have_product_group_id = true; 390 continue; 391 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_MONEY_POT_ID: 392 ppph->money_pot_id = options[i].details.money_pot_id; 393 ppph->have_money_pot_id = true; 394 continue; 395 case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_PRICE_IS_NET: 396 ppph->price_is_net = options[i].details.price_is_net; 397 ppph->have_price_is_net = true; 398 continue; 399 } 400 GNUNET_break (0); 401 return GNUNET_SYSERR; 402 } 403 return GNUNET_OK; 404 } 405 406 407 enum TALER_ErrorCode 408 TALER_MERCHANT_post_private_products_start ( 409 struct TALER_MERCHANT_PostPrivateProductsHandle *ppph, 410 TALER_MERCHANT_PostPrivateProductsCallback cb, 411 TALER_MERCHANT_POST_PRIVATE_PRODUCTS_RESULT_CLOSURE *cb_cls) 412 { 413 json_t *req_obj; 414 json_t *categories; 415 CURL *eh; 416 char unit_total_stock_buf[64]; 417 418 ppph->cb = cb; 419 ppph->cb_cls = cb_cls; 420 ppph->url = TALER_url_join (ppph->base_url, 421 "private/products", 422 NULL); 423 if (NULL == ppph->url) 424 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 425 426 TALER_MERCHANT_format_stock_string (ppph->total_stock, 427 ppph->total_stock_frac, 428 unit_total_stock_buf, 429 sizeof (unit_total_stock_buf)); 430 431 if (0 == ppph->num_cats) 432 { 433 categories = NULL; 434 } 435 else 436 { 437 categories = json_array (); 438 GNUNET_assert (NULL != categories); 439 for (unsigned int i = 0; i < ppph->num_cats; i++) 440 GNUNET_assert (0 == 441 json_array_append_new (categories, 442 json_integer (ppph->cats[i]))); 443 } 444 445 req_obj = GNUNET_JSON_PACK ( 446 GNUNET_JSON_pack_string ("product_id", 447 ppph->product_id), 448 GNUNET_JSON_pack_string ("product_name", 449 (NULL != ppph->product_name) 450 ? ppph->product_name 451 : ppph->description), 452 GNUNET_JSON_pack_string ("description", 453 ppph->description), 454 GNUNET_JSON_pack_allow_null ( 455 GNUNET_JSON_pack_object_incref ("description_i18n", 456 ppph->description_i18n)), 457 GNUNET_JSON_pack_allow_null ( 458 GNUNET_JSON_pack_array_steal ("categories", 459 categories)), 460 GNUNET_JSON_pack_string ("unit", 461 ppph->unit), 462 TALER_JSON_pack_amount_array ("unit_price", 463 ppph->unit_prices_len, 464 ppph->unit_prices), 465 GNUNET_JSON_pack_allow_null ( 466 GNUNET_JSON_pack_string ("image", 467 ppph->image)), 468 GNUNET_JSON_pack_allow_null ( 469 GNUNET_JSON_pack_array_incref ("taxes", 470 ppph->taxes)), 471 GNUNET_JSON_pack_string ("unit_total_stock", 472 unit_total_stock_buf), 473 GNUNET_JSON_pack_allow_null ( 474 GNUNET_JSON_pack_object_incref ("address", 475 ppph->address)), 476 GNUNET_JSON_pack_allow_null ( 477 GNUNET_JSON_pack_timestamp ("next_restock", 478 ppph->have_next_restock 479 ? ppph->next_restock 480 : GNUNET_TIME_UNIT_ZERO_TS))); 481 if (ppph->have_minimum_age) 482 { 483 GNUNET_assert (0 == 484 json_object_set_new (req_obj, 485 "minimum_age", 486 json_integer ( 487 ppph->minimum_age))); 488 } 489 if (ppph->have_product_group_id) 490 { 491 GNUNET_assert (0 == 492 json_object_set_new (req_obj, 493 "product_group_id", 494 json_integer ( 495 ppph->product_group_id))); 496 } 497 if (ppph->have_money_pot_id) 498 { 499 GNUNET_assert (0 == 500 json_object_set_new (req_obj, 501 "money_pot_id", 502 json_integer ( 503 ppph->money_pot_id))); 504 } 505 if (ppph->have_price_is_net) 506 { 507 GNUNET_assert (0 == 508 json_object_set_new (req_obj, 509 "price_is_net", 510 json_boolean ( 511 ppph->price_is_net))); 512 } 513 if (ppph->have_unit_allow_fraction && 514 ppph->unit_allow_fraction) 515 { 516 GNUNET_assert (0 == 517 json_object_set_new (req_obj, 518 "unit_allow_fraction", 519 json_true ())); 520 if (ppph->have_unit_precision_level) 521 { 522 GNUNET_assert (0 == 523 json_object_set_new (req_obj, 524 "unit_precision_level", 525 json_integer ( 526 ppph->unit_precision_level))); 527 } 528 } 529 eh = TALER_MERCHANT_curl_easy_get_ (ppph->url); 530 if ( (NULL == eh) || 531 (GNUNET_OK != 532 TALER_curl_easy_post (&ppph->post_ctx, 533 eh, 534 req_obj)) ) 535 { 536 GNUNET_break (0); 537 json_decref (req_obj); 538 if (NULL != eh) 539 curl_easy_cleanup (eh); 540 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 541 } 542 json_decref (req_obj); 543 ppph->job = GNUNET_CURL_job_add2 (ppph->ctx, 544 eh, 545 ppph->post_ctx.headers, 546 &handle_post_products_finished, 547 ppph); 548 if (NULL == ppph->job) 549 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 550 return TALER_EC_NONE; 551 } 552 553 554 void 555 TALER_MERCHANT_post_private_products_cancel ( 556 struct TALER_MERCHANT_PostPrivateProductsHandle *ppph) 557 { 558 if (NULL != ppph->job) 559 { 560 GNUNET_CURL_job_cancel (ppph->job); 561 ppph->job = NULL; 562 } 563 TALER_curl_easy_post_finished (&ppph->post_ctx); 564 json_decref (ppph->description_i18n); 565 json_decref (ppph->taxes); 566 json_decref (ppph->address); 567 GNUNET_free (ppph->cats); 568 GNUNET_free (ppph->product_id); 569 GNUNET_free (ppph->description); 570 GNUNET_free (ppph->unit); 571 GNUNET_free (ppph->image); 572 GNUNET_free (ppph->product_name); 573 GNUNET_free (ppph->url); 574 GNUNET_free (ppph->base_url); 575 GNUNET_free (ppph); 576 } 577 578 579 /* end of merchant_api_post-private-products-new.c */