taler-merchant-httpd_get-templates-TEMPLATE_ID.c (17891B)
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 GNUNET_free (product_ids); 397 GNUNET_free (category_ids); 398 if (0 > qs) 399 { 400 GNUNET_break (0); 401 inventory_payload_cleanup (&ipc); 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 410 ipc.category_payload = json_array (); 411 GNUNET_assert (NULL != ipc.category_payload); 412 if (0 < ipc.category_set.len) 413 { 414 enum GNUNET_DB_QueryStatus qs; 415 416 qs = TALER_MERCHANTDB_lookup_categories_by_ids ( 417 TMH_db, 418 mi->settings.id, 419 ipc.category_set.ids, 420 ipc.category_set.len, 421 &add_inventory_category, 422 &ipc); 423 if (0 > qs) 424 { 425 GNUNET_break (0); 426 inventory_payload_cleanup (&ipc); 427 return TALER_MHD_reply_with_error ( 428 connection, 429 MHD_HTTP_INTERNAL_SERVER_ERROR, 430 TALER_EC_GENERIC_DB_FETCH_FAILED, 431 "lookup_categories_by_ids"); 432 } 433 } 434 435 ipc.unit_payload = json_array (); 436 GNUNET_assert (NULL != ipc.unit_payload); 437 if (0 < ipc.unit_set.len) 438 { 439 enum GNUNET_DB_QueryStatus qs; 440 441 qs = TALER_MERCHANTDB_lookup_custom_units_by_names ( 442 TMH_db, 443 mi->settings.id, 444 (const char *const *) ipc.unit_set.units, 445 ipc.unit_set.len, 446 &add_inventory_unit, 447 &ipc); 448 if (0 > qs) 449 { 450 GNUNET_break (0); 451 inventory_payload_cleanup (&ipc); 452 return TALER_MHD_reply_with_error ( 453 connection, 454 MHD_HTTP_INTERNAL_SERVER_ERROR, 455 TALER_EC_GENERIC_DB_FETCH_FAILED, 456 "lookup_custom_units_by_names"); 457 } 458 } 459 460 inventory_payload = GNUNET_JSON_PACK ( 461 GNUNET_JSON_pack_array_steal ("products", 462 ipc.products), 463 GNUNET_JSON_pack_array_steal ("categories", 464 ipc.category_payload), 465 GNUNET_JSON_pack_array_steal ("units", 466 ipc.unit_payload)); 467 ipc.products = NULL; 468 ipc.category_payload = NULL; 469 ipc.unit_payload = NULL; 470 471 template_contract = json_deep_copy (tp->template_contract); 472 GNUNET_assert (NULL != template_contract); 473 /* remove internal fields */ 474 (void) json_object_del (template_contract, 475 "selected_categories"); 476 (void) json_object_del (template_contract, 477 "selected_products"); 478 (void) json_object_del (template_contract, 479 "selected_all"); 480 /* add inventory data */ 481 GNUNET_assert (0 == 482 json_object_set_new (template_contract, 483 "inventory_payload", 484 inventory_payload)); 485 { 486 enum MHD_Result ret; 487 488 ret = TALER_MHD_REPLY_JSON_PACK ( 489 connection, 490 MHD_HTTP_OK, 491 GNUNET_JSON_pack_allow_null ( 492 GNUNET_JSON_pack_object_incref ("editable_defaults", 493 tp->editable_defaults)), 494 GNUNET_JSON_pack_allow_null ( 495 GNUNET_JSON_pack_string ("required_currency", 496 NULL /* FIXME: add support */)), 497 GNUNET_JSON_pack_object_steal ("template_contract", 498 template_contract)); 499 inventory_payload_cleanup (&ipc); 500 return ret; 501 } 502 } 503 504 505 enum MHD_Result 506 TMH_get_templates_ID ( 507 const struct TMH_RequestHandler *rh, 508 struct MHD_Connection *connection, 509 struct TMH_HandlerContext *hc) 510 { 511 struct TMH_MerchantInstance *mi = hc->instance; 512 struct TALER_MERCHANTDB_TemplateDetails tp = { 0 }; 513 enum GNUNET_DB_QueryStatus qs; 514 515 GNUNET_assert (NULL != mi); 516 qs = TALER_MERCHANTDB_lookup_template (TMH_db, 517 mi->settings.id, 518 hc->infix, 519 &tp); 520 if (0 > qs) 521 { 522 GNUNET_break (0); 523 return TALER_MHD_reply_with_error ( 524 connection, 525 MHD_HTTP_INTERNAL_SERVER_ERROR, 526 TALER_EC_GENERIC_DB_FETCH_FAILED, 527 "lookup_template"); 528 } 529 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 530 { 531 return TALER_MHD_reply_with_error ( 532 connection, 533 MHD_HTTP_NOT_FOUND, 534 TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, 535 hc->infix); 536 } 537 { 538 enum MHD_Result ret; 539 540 switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract)) 541 { 542 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 543 ret = handle_get_templates_inventory (connection, 544 mi, 545 &tp); 546 TALER_MERCHANTDB_template_details_free (&tp); 547 return ret; 548 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 549 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 550 ret = TALER_MHD_REPLY_JSON_PACK ( 551 connection, 552 MHD_HTTP_OK, 553 GNUNET_JSON_pack_allow_null ( 554 GNUNET_JSON_pack_object_incref ("editable_defaults", 555 tp.editable_defaults)), 556 GNUNET_JSON_pack_allow_null ( 557 GNUNET_JSON_pack_string ("required_currency", 558 NULL /* FIXME: add support */)), 559 GNUNET_JSON_pack_object_incref ("template_contract", 560 tp.template_contract)); 561 TALER_MERCHANTDB_template_details_free (&tp); 562 return ret; 563 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 564 break; 565 } 566 GNUNET_break_op (0); 567 ret = TALER_MHD_reply_with_error ( 568 connection, 569 MHD_HTTP_INTERNAL_SERVER_ERROR, 570 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 571 "template_type"); 572 TALER_MERCHANTDB_template_details_free (&tp); 573 return ret; 574 } 575 } 576 577 578 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */