merchant_api_post-private-templates.c (15369B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Lesser General Public License as 7 published by the Free Software Foundation; either version 2.1, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General 16 Public License along with TALER; see the file COPYING.LGPL. 17 If not, see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file merchant_api_post-private-templates.c 21 * @brief Implementation of the POST /templates request 22 * of the merchant's HTTP API 23 * @author Priscilla HUANG 24 */ 25 #include "taler/platform.h" 26 #include <curl/curl.h> 27 #include <jansson.h> 28 #include <microhttpd.h> /* just for HTTP status codes */ 29 #include <gnunet/gnunet_util_lib.h> 30 #include "taler/taler_merchant_service.h" 31 #include "merchant_api_curl_defaults.h" 32 #include "merchant_api_common.h" 33 #include <taler/taler_json_lib.h> 34 #include <taler/taler_curl_lib.h> 35 #include "taler/taler_merchant_util.h" 36 37 /* FIXME: Bohdan is to stupid to figure out how util can be used here */ 38 static enum TALER_MERCHANT_TemplateType 39 template_type_from_string (const char *template_type) 40 { 41 if (NULL == template_type) 42 return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; 43 if (0 == strcmp (template_type, 44 "fixed-order")) 45 return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER; 46 if (0 == strcmp (template_type, 47 "inventory-cart")) 48 return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART; 49 if (0 == strcmp (template_type, 50 "paivana")) 51 return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA; 52 return TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 53 } 54 55 56 /** 57 * Validate a fixed-order template contract. 58 * 59 * @param template_contract JSON template contract 60 * @param summary summary pointer 61 * @param amount amount storage 62 * @param minimum_age minimum age storage 63 * @param pay_duration pay duration storage 64 * @return true if valid 65 */ 66 static bool 67 validate_template_contract_fixed (const json_t *template_contract, 68 const char **summary, 69 struct TALER_Amount *amount, 70 uint32_t *minimum_age, 71 struct GNUNET_TIME_Relative *pay_duration) 72 { 73 struct GNUNET_JSON_Specification spec[] = { 74 GNUNET_JSON_spec_mark_optional ( 75 GNUNET_JSON_spec_string ("summary", 76 summary), 77 NULL), 78 GNUNET_JSON_spec_mark_optional ( 79 TALER_JSON_spec_amount_any ("amount", 80 amount), 81 NULL), 82 GNUNET_JSON_spec_uint32 ("minimum_age", 83 minimum_age), 84 GNUNET_JSON_spec_relative_time ("pay_duration", 85 pay_duration), 86 GNUNET_JSON_spec_end () 87 }; 88 const char *ename; 89 unsigned int eline; 90 91 if (GNUNET_OK != 92 GNUNET_JSON_parse (template_contract, 93 spec, 94 &ename, 95 &eline)) 96 { 97 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 98 "Invalid template_contract for field %s\n", 99 ename); 100 return false; 101 } 102 return true; 103 } 104 105 106 /** 107 * Validate an inventory-cart template contract. 108 * 109 * @param template_contract JSON template contract 110 * @param summary summary pointer 111 * @param pay_duration pay duration storage 112 * @return true if valid 113 */ 114 static bool 115 validate_template_contract_inventory (const json_t *template_contract, 116 const char **summary, 117 struct GNUNET_TIME_Relative *pay_duration) 118 { 119 bool selected_all = false; 120 bool choose_one = false; 121 bool request_tip = false; 122 const json_t *selected_categories = NULL; 123 const json_t *selected_products = NULL; 124 struct GNUNET_JSON_Specification ispec[] = { 125 GNUNET_JSON_spec_mark_optional ( 126 GNUNET_JSON_spec_string ("summary", 127 summary), 128 NULL), 129 GNUNET_JSON_spec_mark_optional ( 130 GNUNET_JSON_spec_bool ("request_tip", 131 &request_tip), 132 NULL), 133 GNUNET_JSON_spec_relative_time ("pay_duration", 134 pay_duration), 135 GNUNET_JSON_spec_mark_optional ( 136 GNUNET_JSON_spec_bool ("selected_all", 137 &selected_all), 138 NULL), 139 GNUNET_JSON_spec_mark_optional ( 140 GNUNET_JSON_spec_array_const ("selected_categories", 141 &selected_categories), 142 NULL), 143 GNUNET_JSON_spec_mark_optional ( 144 GNUNET_JSON_spec_array_const ("selected_products", 145 &selected_products), 146 NULL), 147 GNUNET_JSON_spec_mark_optional ( 148 GNUNET_JSON_spec_bool ("choose_one", 149 &choose_one), 150 NULL), 151 GNUNET_JSON_spec_end () 152 }; 153 const char *ename; 154 unsigned int eline; 155 156 (void) request_tip; 157 (void) selected_all; 158 (void) choose_one; 159 (void) selected_categories; 160 (void) selected_products; 161 if (GNUNET_OK != 162 GNUNET_JSON_parse (template_contract, 163 ispec, 164 &ename, 165 &eline)) 166 { 167 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 168 "Invalid template_contract for field %s\n", 169 ename); 170 return false; 171 } 172 return true; 173 } 174 175 176 /** 177 * Validate a paivana template contract. 178 * 179 * @param template_contract JSON template contract 180 * @param summary summary pointer 181 * @param amount amount storage 182 * @param minimum_age minimum age storage 183 * @param pay_duration pay duration storage 184 * @return true if valid 185 */ 186 static bool 187 validate_template_contract_paivana (const json_t *template_contract, 188 const char **summary, 189 struct TALER_Amount *amount, 190 uint32_t *minimum_age, 191 struct GNUNET_TIME_Relative *pay_duration) 192 { 193 /* TODO: PAIVANA validate paivana-specific fields beyond presence. */ 194 const char *paivana_id; 195 struct GNUNET_JSON_Specification pspec[] = { 196 GNUNET_JSON_spec_string ("paivana_id", 197 &paivana_id), 198 GNUNET_JSON_spec_end () 199 }; 200 const char *ename; 201 unsigned int eline; 202 203 if (GNUNET_OK != 204 GNUNET_JSON_parse (template_contract, 205 pspec, 206 &ename, 207 &eline)) 208 { 209 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 210 "Invalid paivana template_contract for field %s\n", 211 ename); 212 return false; 213 } 214 215 if (! validate_template_contract_fixed (template_contract, 216 summary, 217 amount, 218 minimum_age, 219 pay_duration)) 220 return false; 221 222 (void) paivana_id; 223 return true; 224 } 225 226 227 /** 228 * Handle for a POST /templates/$ID operation. 229 */ 230 struct TALER_MERCHANT_TemplatesPostHandle 231 { 232 233 /** 234 * The url for this request. 235 */ 236 char *url; 237 238 /** 239 * Handle for the request. 240 */ 241 struct GNUNET_CURL_Job *job; 242 243 /** 244 * Function to call with the result. 245 */ 246 TALER_MERCHANT_TemplatesPostCallback cb; 247 248 /** 249 * Closure for @a cb. 250 */ 251 void *cb_cls; 252 253 /** 254 * Reference to the execution context. 255 */ 256 struct GNUNET_CURL_Context *ctx; 257 258 /** 259 * Minor context that holds body and headers. 260 */ 261 struct TALER_CURL_PostContext post_ctx; 262 }; 263 264 265 /** 266 * Function called when we're done processing the 267 * HTTP POST /templates request. 268 * 269 * @param cls the `struct TALER_MERCHANT_TemplatesPostHandle` 270 * @param response_code HTTP response code, 0 on error 271 * @param response response body, NULL if not in JSON 272 */ 273 static void 274 handle_post_templates_finished (void *cls, 275 long response_code, 276 const void *response) 277 { 278 struct TALER_MERCHANT_TemplatesPostHandle *tph = cls; 279 const json_t *json = response; 280 struct TALER_MERCHANT_HttpResponse hr = { 281 .http_status = (unsigned int) response_code, 282 .reply = json 283 }; 284 285 tph->job = NULL; 286 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 287 "POST /templates completed with response code %u\n", 288 (unsigned int) response_code); 289 switch (response_code) 290 { 291 case 0: 292 hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 293 break; 294 case MHD_HTTP_NO_CONTENT: 295 break; 296 case MHD_HTTP_BAD_REQUEST: 297 hr.ec = TALER_JSON_get_error_code (json); 298 hr.hint = TALER_JSON_get_error_hint (json); 299 /* This should never happen, either us 300 * or the merchant is buggy (or API version conflict); 301 * just pass JSON reply to the application */ 302 break; 303 case MHD_HTTP_UNAUTHORIZED: 304 hr.ec = TALER_JSON_get_error_code (json); 305 hr.hint = TALER_JSON_get_error_hint (json); 306 /* Nothing really to verify, merchant says we need to authenticate. */ 307 break; 308 case MHD_HTTP_FORBIDDEN: 309 hr.ec = TALER_JSON_get_error_code (json); 310 hr.hint = TALER_JSON_get_error_hint (json); 311 /* Nothing really to verify, merchant says we tried to abort the payment 312 * after it was successful. We should pass the JSON reply to the 313 * application */ 314 break; 315 case MHD_HTTP_NOT_FOUND: 316 hr.ec = TALER_JSON_get_error_code (json); 317 hr.hint = TALER_JSON_get_error_hint (json); 318 /* Nothing really to verify, this should never 319 happen, we should pass the JSON reply to the 320 application */ 321 break; 322 case MHD_HTTP_CONFLICT: 323 hr.ec = TALER_JSON_get_error_code (json); 324 hr.hint = TALER_JSON_get_error_hint (json); 325 break; 326 case MHD_HTTP_INTERNAL_SERVER_ERROR: 327 hr.ec = TALER_JSON_get_error_code (json); 328 hr.hint = TALER_JSON_get_error_hint (json); 329 /* Server had an internal issue; we should retry, 330 but this API leaves this to the application */ 331 break; 332 default: 333 TALER_MERCHANT_parse_error_details_ (json, 334 response_code, 335 &hr); 336 /* unexpected response code */ 337 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 338 "Unexpected response code %u/%d\n", 339 (unsigned int) response_code, 340 (int) hr.ec); 341 GNUNET_break_op (0); 342 break; 343 } 344 tph->cb (tph->cb_cls, 345 &hr); 346 TALER_MERCHANT_templates_post_cancel (tph); 347 } 348 349 350 static bool 351 test_template_contract_valid (const json_t *template_contract) 352 { 353 const char *template_type = NULL; 354 enum TALER_MERCHANT_TemplateType template_type_enum; 355 struct GNUNET_JSON_Specification type_spec[] = { 356 GNUNET_JSON_spec_mark_optional ( 357 GNUNET_JSON_spec_string ("template_type", 358 &template_type), 359 NULL), 360 GNUNET_JSON_spec_end () 361 }; 362 const char *summary; 363 struct TALER_Amount amount = { .value = 0}; 364 uint32_t minimum_age = 0; 365 struct GNUNET_TIME_Relative pay_duration = { 0 }; 366 const char *ename; 367 unsigned int eline; 368 369 if (GNUNET_OK != 370 GNUNET_JSON_parse (template_contract, 371 type_spec, 372 &ename, 373 &eline)) 374 { 375 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 376 "Invalid template_contract for field %s\n", 377 ename); 378 return false; 379 } 380 381 template_type_enum = template_type_from_string (template_type); 382 if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == template_type_enum) 383 { 384 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 385 "Invalid template_type '%s'\n", 386 template_type); 387 return false; 388 } 389 390 /* FIXME: Bohdan understands that links have to be changed, but worried, that can crash something */ 391 switch (template_type_enum) 392 { 393 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 394 return validate_template_contract_inventory (template_contract, 395 &summary, 396 &pay_duration); 397 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 398 return validate_template_contract_fixed (template_contract, 399 &summary, 400 &amount, 401 &minimum_age, 402 &pay_duration); 403 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 404 return validate_template_contract_paivana (template_contract, 405 &summary, 406 &amount, 407 &minimum_age, 408 &pay_duration); 409 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 410 break; 411 } 412 return false; 413 } 414 415 416 struct TALER_MERCHANT_TemplatesPostHandle * 417 TALER_MERCHANT_templates_post ( 418 struct GNUNET_CURL_Context *ctx, 419 const char *backend_url, 420 const char *template_id, 421 const char *template_description, 422 const char *otp_id, 423 const json_t *template_contract, 424 TALER_MERCHANT_TemplatesPostCallback cb, 425 void *cb_cls) 426 { 427 struct TALER_MERCHANT_TemplatesPostHandle *tph; 428 json_t *req_obj; 429 430 if (! test_template_contract_valid (template_contract)) 431 { 432 GNUNET_break (0); 433 return NULL; 434 } 435 req_obj = GNUNET_JSON_PACK ( 436 GNUNET_JSON_pack_string ("template_id", 437 template_id), 438 GNUNET_JSON_pack_string ("template_description", 439 template_description), 440 GNUNET_JSON_pack_allow_null ( 441 GNUNET_JSON_pack_string ("otp_id", 442 otp_id)), 443 GNUNET_JSON_pack_object_incref ("template_contract", 444 (json_t *) template_contract)); 445 tph = GNUNET_new (struct TALER_MERCHANT_TemplatesPostHandle); 446 tph->ctx = ctx; 447 tph->cb = cb; 448 tph->cb_cls = cb_cls; 449 tph->url = TALER_url_join (backend_url, 450 "private/templates", 451 NULL); 452 if (NULL == tph->url) 453 { 454 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 455 "Could not construct request URL.\n"); 456 json_decref (req_obj); 457 GNUNET_free (tph); 458 return NULL; 459 } 460 { 461 CURL *eh; 462 463 eh = TALER_MERCHANT_curl_easy_get_ (tph->url); 464 GNUNET_assert (GNUNET_OK == 465 TALER_curl_easy_post (&tph->post_ctx, 466 eh, 467 req_obj)); 468 json_decref (req_obj); 469 tph->job = GNUNET_CURL_job_add2 (ctx, 470 eh, 471 tph->post_ctx.headers, 472 &handle_post_templates_finished, 473 tph); 474 GNUNET_assert (NULL != tph->job); 475 } 476 return tph; 477 } 478 479 480 void 481 TALER_MERCHANT_templates_post_cancel ( 482 struct TALER_MERCHANT_TemplatesPostHandle *tph) 483 { 484 if (NULL != tph->job) 485 { 486 GNUNET_CURL_job_cancel (tph->job); 487 tph->job = NULL; 488 } 489 TALER_curl_easy_post_finished (&tph->post_ctx); 490 GNUNET_free (tph->url); 491 GNUNET_free (tph); 492 } 493 494 495 /* end of merchant_api_post_templates.c */