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