taler-merchant-httpd_get-templates-TEMPLATE_ID.c (18671B)
1 /* 2 This file is part of TALER 3 (C) 2022-2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file src/backend/taler-merchant-httpd_get-templates-TEMPLATE_ID.c 18 * @brief implement GET /templates/$ID 19 * @author Priscilla HUANG 20 */ 21 #include "platform.h" 22 #include "taler-merchant-httpd_get-templates-TEMPLATE_ID.h" 23 #include "taler-merchant-httpd_helper.h" 24 #include <taler/taler_json_lib.h> 25 #include "merchant-database/iterate_categories_by_ids.h" 26 #include "merchant-database/iterate_custom_units_by_names.h" 27 #include "merchant-database/iterate_inventory_products.h" 28 #include "merchant-database/iterate_inventory_products_filtered.h" 29 #include "merchant-database/get_template.h" 30 31 32 /** 33 * Determine the currency the client must pay in, if the template 34 * requires a particular one without fixing the amount. 35 * 36 * @param template_contract contract of the template 37 * @return currency to require, NULL if the template does not say 38 */ 39 static const char * 40 get_required_currency (const json_t *template_contract) 41 { 42 const json_t *currency; 43 44 currency = json_object_get (template_contract, 45 "currency"); 46 if (NULL == currency) 47 return NULL; 48 if (! json_is_string (currency)) 49 { 50 GNUNET_break (0); 51 return NULL; 52 } 53 return json_string_value (currency); 54 } 55 56 57 /** 58 * Context for building inventory template payloads. 59 */ 60 struct InventoryPayloadContext 61 { 62 /** 63 * Selected category IDs (as JSON array). 64 */ 65 const json_t *selected_categories; 66 67 /** 68 * Selected product IDs (as JSON array) from contract_template. 69 */ 70 const json_t *selected_products; 71 72 /** 73 * Whether all products are selected. 74 */ 75 bool selected_all; 76 77 /** 78 * JSON array of products to build. 79 */ 80 json_t *products; 81 82 /** 83 * JSON array of categories to build. 84 */ 85 json_t *category_payload; 86 87 /** 88 * JSON array of units to build. 89 */ 90 json_t *unit_payload; 91 92 /** 93 * Set of categories referenced by the products. 94 */ 95 struct TMH_CategorySet category_set; 96 97 /** 98 * Set of unit identifiers referenced by the products. 99 */ 100 struct TMH_UnitSet unit_set; 101 }; 102 103 104 /** 105 * Release resources associated with an inventory payload context. 106 * 107 * @param ipc inventory payload context 108 */ 109 static void 110 inventory_payload_cleanup (struct InventoryPayloadContext *ipc) 111 { 112 if (NULL != ipc->products) 113 json_decref (ipc->products); 114 if (NULL != ipc->category_payload) 115 json_decref (ipc->category_payload); 116 if (NULL != ipc->unit_payload) 117 json_decref (ipc->unit_payload); 118 GNUNET_free (ipc->category_set.ids); 119 TMH_unit_set_clear (&ipc->unit_set); 120 ipc->products = NULL; 121 ipc->category_payload = NULL; 122 ipc->unit_payload = NULL; 123 ipc->category_set.len = 0; 124 } 125 126 127 /** 128 * Add inventory product to JSON payload. 129 * 130 * @param cls inventory payload context 131 * @param product_id product identifier 132 * @param pd product details 133 * @param num_categories number of categories 134 * @param categories category IDs 135 */ 136 static void 137 add_inventory_product ( 138 void *cls, 139 const char *product_id, 140 const struct TALER_MERCHANTDB_InventoryProductDetails *pd, 141 size_t num_categories, 142 const uint64_t *categories) 143 { 144 struct InventoryPayloadContext *ipc = cls; 145 json_t *jcategories; 146 json_t *product; 147 char remaining_stock_buf[64]; 148 149 jcategories = json_array (); 150 GNUNET_assert (NULL != jcategories); 151 for (size_t i = 0; i < num_categories; i++) 152 { 153 /* Adding per product category */ 154 TMH_category_set_add (&ipc->category_set, 155 categories[i]); 156 GNUNET_assert (0 == 157 json_array_append_new (jcategories, 158 json_integer (categories[i]))); 159 } 160 GNUNET_assert (0 < pd->price_array_length); 161 TALER_MERCHANT_vk_format_fractional_string ( 162 TALER_MERCHANT_VK_STOCK, 163 pd->remaining_stock, 164 pd->remaining_stock_frac, 165 sizeof (remaining_stock_buf), 166 remaining_stock_buf); 167 product = GNUNET_JSON_PACK ( 168 GNUNET_JSON_pack_string ("product_id", 169 product_id), 170 GNUNET_JSON_pack_string ("product_name", 171 pd->product_name), 172 GNUNET_JSON_pack_string ("description", 173 pd->description), 174 GNUNET_JSON_pack_allow_null ( 175 GNUNET_JSON_pack_object_incref ("description_i18n", 176 pd->description_i18n)), 177 GNUNET_JSON_pack_string ("unit", 178 pd->unit), 179 TALER_JSON_pack_amount_array ("unit_prices", 180 pd->price_array_length, 181 pd->price_array), 182 GNUNET_JSON_pack_bool ("unit_allow_fraction", 183 pd->allow_fractional_quantity), 184 GNUNET_JSON_pack_uint64 ("unit_precision_level", 185 pd->fractional_precision_level), 186 GNUNET_JSON_pack_string ("remaining_stock", 187 remaining_stock_buf), 188 GNUNET_JSON_pack_array_steal ("categories", 189 jcategories), 190 GNUNET_JSON_pack_allow_null ( 191 GNUNET_JSON_pack_array_incref ("taxes", 192 pd->taxes)), 193 GNUNET_JSON_pack_allow_null ( 194 GNUNET_JSON_pack_string ("image_hash", 195 pd->image_hash))); 196 197 /* Adding per product unit */ 198 TMH_unit_set_add (&ipc->unit_set, 199 pd->unit); 200 201 GNUNET_assert (0 == 202 json_array_append_new (ipc->products, 203 product)); 204 } 205 206 207 /** 208 * Add an inventory category to the payload if referenced. 209 * 210 * @param cls category payload context 211 * @param category_id category identifier 212 * @param category_name category name 213 * @param category_name_i18n translated names 214 * @param product_count number of products (unused) 215 */ 216 static void 217 add_inventory_category (void *cls, 218 uint64_t category_id, 219 const char *category_name, 220 const json_t *category_name_i18n, 221 uint64_t product_count) 222 { 223 struct InventoryPayloadContext *ipc = cls; 224 json_t *category; 225 226 (void) product_count; 227 category = GNUNET_JSON_PACK ( 228 GNUNET_JSON_pack_uint64 ("category_id", 229 category_id), 230 GNUNET_JSON_pack_string ("category_name", 231 category_name), 232 GNUNET_JSON_pack_allow_null ( 233 GNUNET_JSON_pack_object_incref ("category_name_i18n", 234 (json_t *) category_name_i18n))); 235 GNUNET_assert (0 == 236 json_array_append_new (ipc->category_payload, 237 category)); 238 } 239 240 241 /** 242 * Add an inventory unit to the payload if referenced and non-builtin. 243 * 244 * @param cls unit payload context 245 * @param unit_serial unit identifier 246 * @param ud unit details 247 */ 248 static void 249 add_inventory_unit (void *cls, 250 uint64_t unit_serial, 251 const struct TALER_MERCHANTDB_UnitDetails *ud) 252 { 253 struct InventoryPayloadContext *ipc = cls; 254 json_t *unit; 255 256 (void) unit_serial; 257 258 unit = GNUNET_JSON_PACK ( 259 GNUNET_JSON_pack_string ("unit", 260 ud->unit), 261 GNUNET_JSON_pack_string ("unit_name_long", 262 ud->unit_name_long), 263 GNUNET_JSON_pack_allow_null ( 264 GNUNET_JSON_pack_object_incref ("unit_name_long_i18n", 265 ud->unit_name_long_i18n)), 266 GNUNET_JSON_pack_string ("unit_name_short", 267 ud->unit_name_short), 268 GNUNET_JSON_pack_allow_null ( 269 GNUNET_JSON_pack_object_incref ("unit_name_short_i18n", 270 ud->unit_name_short_i18n)), 271 GNUNET_JSON_pack_bool ("unit_allow_fraction", 272 ud->unit_allow_fraction), 273 GNUNET_JSON_pack_uint64 ("unit_precision_level", 274 ud->unit_precision_level)); 275 GNUNET_assert (0 == 276 json_array_append_new (ipc->unit_payload, 277 unit)); 278 } 279 280 281 /** 282 * Build wallet-facing payload for inventory templates. 283 * 284 * @param connection HTTP connection 285 * @param mi merchant instance 286 * @param tp template details 287 * @return MHD result 288 */ 289 static enum MHD_Result 290 handle_get_templates_inventory ( 291 struct MHD_Connection *connection, 292 const struct TMH_MerchantInstance *mi, 293 const struct TALER_MERCHANTDB_TemplateDetails *tp) 294 { 295 struct InventoryPayloadContext ipc; 296 const char **product_ids = NULL; 297 uint64_t *category_ids = NULL; 298 size_t num_product_ids = 0; 299 size_t num_category_ids = 0; 300 json_t *inventory_payload; 301 json_t *template_contract; 302 303 memset (&ipc, 304 0, 305 sizeof (ipc)); 306 ipc.products = json_array (); 307 { 308 struct GNUNET_JSON_Specification spec[] = { 309 GNUNET_JSON_spec_mark_optional ( 310 GNUNET_JSON_spec_array_const ("selected_categories", 311 &ipc.selected_categories), 312 NULL), 313 GNUNET_JSON_spec_mark_optional ( 314 GNUNET_JSON_spec_array_const ("selected_products", 315 &ipc.selected_products), 316 NULL), 317 GNUNET_JSON_spec_mark_optional ( 318 GNUNET_JSON_spec_bool ("selected_all", 319 &ipc.selected_all), 320 NULL), 321 GNUNET_JSON_spec_end () 322 }; 323 const char *err_name; 324 unsigned int err_line; 325 326 if (GNUNET_OK != 327 GNUNET_JSON_parse (tp->template_contract, 328 spec, 329 &err_name, 330 &err_line)) 331 { 332 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 333 "Invalid inventory template_contract for field %s\n", 334 err_name); 335 inventory_payload_cleanup (&ipc); 336 return TALER_MHD_reply_with_error ( 337 connection, 338 MHD_HTTP_INTERNAL_SERVER_ERROR, 339 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 340 err_name); 341 } 342 } 343 344 if (! ipc.selected_all) 345 { 346 if (NULL != ipc.selected_products) 347 { 348 size_t max_ids; 349 350 max_ids = json_array_size (ipc.selected_products); 351 if (0 < max_ids) 352 product_ids = GNUNET_new_array (max_ids, 353 const char *); 354 for (size_t i = 0; i < max_ids; i++) 355 { 356 const json_t *entry = json_array_get (ipc.selected_products, 357 i); 358 359 if (json_is_string (entry)) 360 product_ids[num_product_ids++] = json_string_value (entry); 361 else 362 GNUNET_break (0); 363 } 364 } 365 if (NULL != ipc.selected_categories) 366 { 367 size_t max_categories; 368 369 max_categories = json_array_size (ipc.selected_categories); 370 if (0 < max_categories) 371 category_ids = GNUNET_new_array (max_categories, 372 uint64_t); 373 for (size_t i = 0; i < max_categories; i++) 374 { 375 const json_t *entry = json_array_get (ipc.selected_categories, 376 i); 377 378 if (json_is_integer (entry) && 379 (0 < json_integer_value (entry))) 380 category_ids[num_category_ids++] 381 = (uint64_t) json_integer_value (entry); 382 else 383 GNUNET_break (0); 384 } 385 } 386 } 387 388 if (ipc.selected_all) 389 { 390 enum GNUNET_DB_QueryStatus qs; 391 392 qs = TALER_MERCHANTDB_iterate_inventory_products (TMH_db, 393 mi->settings.id, 394 &add_inventory_product, 395 &ipc); 396 if (0 > qs) 397 { 398 GNUNET_break (0); 399 inventory_payload_cleanup (&ipc); 400 return TALER_MHD_reply_with_error ( 401 connection, 402 MHD_HTTP_INTERNAL_SERVER_ERROR, 403 TALER_EC_GENERIC_DB_FETCH_FAILED, 404 "iterate_inventory_products"); 405 } 406 } 407 else if ( (0 < num_product_ids) || 408 (0 < num_category_ids) ) 409 { 410 enum GNUNET_DB_QueryStatus qs; 411 412 qs = TALER_MERCHANTDB_iterate_inventory_products_filtered ( 413 TMH_db, 414 mi->settings.id, 415 product_ids, 416 num_product_ids, 417 category_ids, 418 num_category_ids, 419 &add_inventory_product, 420 &ipc); 421 if (0 > qs) 422 { 423 GNUNET_break (0); 424 inventory_payload_cleanup (&ipc); 425 GNUNET_free (product_ids); 426 GNUNET_free (category_ids); 427 return TALER_MHD_reply_with_error ( 428 connection, 429 MHD_HTTP_INTERNAL_SERVER_ERROR, 430 TALER_EC_GENERIC_DB_FETCH_FAILED, 431 "iterate_inventory_products_filtered"); 432 } 433 } 434 GNUNET_free (product_ids); 435 GNUNET_free (category_ids); 436 437 ipc.category_payload = json_array (); 438 GNUNET_assert (NULL != ipc.category_payload); 439 if (0 < ipc.category_set.len) 440 { 441 enum GNUNET_DB_QueryStatus qs; 442 443 qs = TALER_MERCHANTDB_iterate_categories_by_ids ( 444 TMH_db, 445 mi->settings.id, 446 ipc.category_set.ids, 447 ipc.category_set.len, 448 &add_inventory_category, 449 &ipc); 450 if (0 > qs) 451 { 452 GNUNET_break (0); 453 inventory_payload_cleanup (&ipc); 454 return TALER_MHD_reply_with_error ( 455 connection, 456 MHD_HTTP_INTERNAL_SERVER_ERROR, 457 TALER_EC_GENERIC_DB_FETCH_FAILED, 458 "iterate_categories_by_ids"); 459 } 460 } 461 462 ipc.unit_payload = json_array (); 463 GNUNET_assert (NULL != ipc.unit_payload); 464 if (0 < ipc.unit_set.len) 465 { 466 enum GNUNET_DB_QueryStatus qs; 467 468 qs = TALER_MERCHANTDB_iterate_custom_units_by_names ( 469 TMH_db, 470 mi->settings.id, 471 (const char *const *) ipc.unit_set.units, 472 ipc.unit_set.len, 473 &add_inventory_unit, 474 &ipc); 475 if (0 > qs) 476 { 477 GNUNET_break (0); 478 inventory_payload_cleanup (&ipc); 479 return TALER_MHD_reply_with_error ( 480 connection, 481 MHD_HTTP_INTERNAL_SERVER_ERROR, 482 TALER_EC_GENERIC_DB_FETCH_FAILED, 483 "iterate_custom_units_by_names"); 484 } 485 } 486 487 inventory_payload = GNUNET_JSON_PACK ( 488 GNUNET_JSON_pack_array_steal ("products", 489 ipc.products), 490 GNUNET_JSON_pack_array_steal ("categories", 491 ipc.category_payload), 492 GNUNET_JSON_pack_array_steal ("units", 493 ipc.unit_payload)); 494 ipc.products = NULL; 495 ipc.category_payload = NULL; 496 ipc.unit_payload = NULL; 497 498 template_contract = json_deep_copy (tp->template_contract); 499 GNUNET_assert (NULL != template_contract); 500 /* remove internal fields */ 501 (void) json_object_del (template_contract, 502 "selected_categories"); 503 (void) json_object_del (template_contract, 504 "selected_products"); 505 (void) json_object_del (template_contract, 506 "selected_all"); 507 /* add inventory data */ 508 GNUNET_assert (0 == 509 json_object_set_new (template_contract, 510 "inventory_payload", 511 inventory_payload)); 512 { 513 enum MHD_Result ret; 514 515 ret = TALER_MHD_REPLY_JSON_PACK ( 516 connection, 517 MHD_HTTP_OK, 518 GNUNET_JSON_pack_allow_null ( 519 GNUNET_JSON_pack_object_incref ("editable_defaults", 520 tp->editable_defaults)), 521 GNUNET_JSON_pack_allow_null ( 522 GNUNET_JSON_pack_string ("required_currency", 523 get_required_currency ( 524 tp->template_contract))), 525 GNUNET_JSON_pack_object_steal ("template_contract", 526 template_contract)); 527 inventory_payload_cleanup (&ipc); 528 return ret; 529 } 530 } 531 532 533 enum MHD_Result 534 TMH_get_templates_ID ( 535 const struct TMH_RequestHandler *rh, 536 struct MHD_Connection *connection, 537 struct TMH_HandlerContext *hc) 538 { 539 struct TMH_MerchantInstance *mi = hc->instance; 540 struct TALER_MERCHANTDB_TemplateDetails tp = { 0 }; 541 enum GNUNET_DB_QueryStatus qs; 542 543 GNUNET_assert (NULL != mi); 544 qs = TALER_MERCHANTDB_get_template (TMH_db, 545 mi->settings.id, 546 hc->infix, 547 &tp); 548 if (0 > qs) 549 { 550 GNUNET_break (0); 551 return TALER_MHD_reply_with_error ( 552 connection, 553 MHD_HTTP_INTERNAL_SERVER_ERROR, 554 TALER_EC_GENERIC_DB_FETCH_FAILED, 555 "get_template"); 556 } 557 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 558 { 559 return TALER_MHD_reply_with_error ( 560 connection, 561 MHD_HTTP_NOT_FOUND, 562 TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, 563 hc->infix); 564 } 565 { 566 enum MHD_Result ret; 567 568 switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract)) 569 { 570 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 571 ret = handle_get_templates_inventory (connection, 572 mi, 573 &tp); 574 TALER_MERCHANTDB_template_details_free (&tp); 575 return ret; 576 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 577 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 578 ret = TALER_MHD_REPLY_JSON_PACK ( 579 connection, 580 MHD_HTTP_OK, 581 GNUNET_JSON_pack_allow_null ( 582 GNUNET_JSON_pack_object_incref ("editable_defaults", 583 tp.editable_defaults)), 584 GNUNET_JSON_pack_allow_null ( 585 GNUNET_JSON_pack_string ("required_currency", 586 get_required_currency ( 587 tp.template_contract))), 588 GNUNET_JSON_pack_object_incref ("template_contract", 589 tp.template_contract)); 590 TALER_MERCHANTDB_template_details_free (&tp); 591 return ret; 592 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 593 break; 594 } 595 GNUNET_break_op (0); 596 ret = TALER_MHD_reply_with_error ( 597 connection, 598 MHD_HTTP_INTERNAL_SERVER_ERROR, 599 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 600 "template_type"); 601 TALER_MERCHANTDB_template_details_free (&tp); 602 return ret; 603 } 604 } 605 606 607 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */