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