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