merchant_api_post_products.c (14019B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-2024 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_products.c 21 * @brief Implementation of the POST /products request 22 * of the merchant's HTTP API 23 * @author Christian Grothoff 24 */ 25 #include "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_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 36 37 /** 38 * Handle for a POST /products/$ID operation. 39 */ 40 struct TALER_MERCHANT_ProductsPostHandle 41 { 42 43 /** 44 * The url for this request. 45 */ 46 char *url; 47 48 /** 49 * Handle for the request. 50 */ 51 struct GNUNET_CURL_Job *job; 52 53 /** 54 * Function to call with the result. 55 */ 56 TALER_MERCHANT_ProductsPostCallback cb; 57 58 /** 59 * Closure for @a cb. 60 */ 61 void *cb_cls; 62 63 /** 64 * Reference to the execution context. 65 */ 66 struct GNUNET_CURL_Context *ctx; 67 68 /** 69 * Minor context that holds body and headers. 70 */ 71 struct TALER_CURL_PostContext post_ctx; 72 73 }; 74 75 76 /** 77 * Function called when we're done processing the 78 * HTTP POST /products request. 79 * 80 * @param cls the `struct TALER_MERCHANT_ProductsPostHandle` 81 * @param response_code HTTP response code, 0 on error 82 * @param response response body, NULL if not in JSON 83 */ 84 static void 85 handle_post_products_finished (void *cls, 86 long response_code, 87 const void *response) 88 { 89 struct TALER_MERCHANT_ProductsPostHandle *pph = cls; 90 const json_t *json = response; 91 struct TALER_MERCHANT_HttpResponse hr = { 92 .http_status = (unsigned int) response_code, 93 .reply = json 94 }; 95 96 pph->job = NULL; 97 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 98 "POST /products completed with response code %u\n", 99 (unsigned int) response_code); 100 switch (response_code) 101 { 102 case 0: 103 hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 104 break; 105 case MHD_HTTP_NO_CONTENT: 106 break; 107 case MHD_HTTP_BAD_REQUEST: 108 hr.ec = TALER_JSON_get_error_code (json); 109 hr.hint = TALER_JSON_get_error_hint (json); 110 /* This should never happen, either us 111 * or the merchant is buggy (or API version conflict); 112 * just pass JSON reply to the application */ 113 break; 114 case MHD_HTTP_UNAUTHORIZED: 115 hr.ec = TALER_JSON_get_error_code (json); 116 hr.hint = TALER_JSON_get_error_hint (json); 117 /* Nothing really to verify, merchant says we need to authenticate. */ 118 break; 119 case MHD_HTTP_FORBIDDEN: 120 hr.ec = TALER_JSON_get_error_code (json); 121 hr.hint = TALER_JSON_get_error_hint (json); 122 /* Nothing really to verify, merchant says we tried to abort the payment 123 * after it was successful. We should pass the JSON reply to the 124 * application */ 125 break; 126 case MHD_HTTP_NOT_FOUND: 127 hr.ec = TALER_JSON_get_error_code (json); 128 hr.hint = TALER_JSON_get_error_hint (json); 129 /* Nothing really to verify, this should never 130 happen, we should pass the JSON reply to the 131 application */ 132 break; 133 case MHD_HTTP_CONFLICT: 134 hr.ec = TALER_JSON_get_error_code (json); 135 hr.hint = TALER_JSON_get_error_hint (json); 136 break; 137 case MHD_HTTP_INTERNAL_SERVER_ERROR: 138 hr.ec = TALER_JSON_get_error_code (json); 139 hr.hint = TALER_JSON_get_error_hint (json); 140 /* Server had an internal issue; we should retry, 141 but this API leaves this to the application */ 142 break; 143 default: 144 TALER_MERCHANT_parse_error_details_ (json, 145 response_code, 146 &hr); 147 /* unexpected response code */ 148 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 149 "Unexpected response code %u/%d\n", 150 (unsigned int) response_code, 151 (int) hr.ec); 152 GNUNET_break_op (0); 153 break; 154 } 155 pph->cb (pph->cb_cls, 156 &hr); 157 TALER_MERCHANT_products_post_cancel (pph); 158 } 159 160 161 struct TALER_MERCHANT_ProductsPostHandle * 162 TALER_MERCHANT_products_post4 ( 163 struct GNUNET_CURL_Context *ctx, 164 const char *backend_url, 165 const char *product_id, 166 const char *description, 167 const json_t *description_i18n, 168 const char *unit, 169 const struct TALER_Amount *unit_prices, 170 size_t unit_price_len, 171 const char *image, 172 const json_t *taxes, 173 int64_t total_stock, 174 uint32_t total_stock_frac, 175 bool unit_allow_fraction, 176 const uint32_t *unit_precision_level, 177 const json_t *address, 178 struct GNUNET_TIME_Timestamp next_restock, 179 uint32_t minimum_age, 180 unsigned int num_cats, 181 const uint64_t *cats, 182 TALER_MERCHANT_ProductsPostCallback cb, 183 void *cb_cls) 184 { 185 struct TALER_MERCHANT_ProductsPostHandle *pph; 186 json_t *req_obj; 187 json_t *categories; 188 char unit_total_stock_buf[64]; 189 190 TALER_MERCHANT_format_stock_string (total_stock, 191 total_stock_frac, 192 unit_total_stock_buf, 193 sizeof (unit_total_stock_buf)); 194 195 if (0 == num_cats) 196 { 197 categories = NULL; 198 } 199 else 200 { 201 categories = json_array (); 202 GNUNET_assert (NULL != categories); 203 for (unsigned int i = 0; i<num_cats; i++) 204 GNUNET_assert (0 == 205 json_array_append_new (categories, 206 json_integer (cats[i]))); 207 } 208 { 209 req_obj = GNUNET_JSON_PACK ( 210 GNUNET_JSON_pack_string ("product_id", 211 product_id), 212 /* FIXME: once we move to the new-style API, 213 allow applications to set the product name properly! */ 214 GNUNET_JSON_pack_string ("product_name", 215 description), 216 GNUNET_JSON_pack_string ("description", 217 description), 218 GNUNET_JSON_pack_allow_null ( 219 GNUNET_JSON_pack_object_incref ("description_i18n", 220 (json_t *) description_i18n)), 221 GNUNET_JSON_pack_allow_null ( 222 GNUNET_JSON_pack_array_steal ("categories", 223 categories)), 224 GNUNET_JSON_pack_string ("unit", 225 unit), 226 TALER_JSON_pack_amount_array ("unit_price", 227 unit_price_len, 228 unit_prices), 229 GNUNET_JSON_pack_string ("image", 230 image), 231 GNUNET_JSON_pack_allow_null ( 232 GNUNET_JSON_pack_array_incref ("taxes", 233 (json_t *) taxes)), 234 GNUNET_JSON_pack_string ("unit_total_stock", 235 unit_total_stock_buf), 236 GNUNET_JSON_pack_bool ("unit_allow_fraction", 237 unit_allow_fraction), 238 GNUNET_JSON_pack_allow_null ( 239 GNUNET_JSON_pack_uint64 ("minimum_age", 240 minimum_age)), 241 GNUNET_JSON_pack_allow_null ( 242 GNUNET_JSON_pack_object_incref ("address", 243 (json_t *) address)), 244 GNUNET_JSON_pack_allow_null ( 245 GNUNET_JSON_pack_timestamp ("next_restock", 246 next_restock))); 247 } 248 if (NULL != unit_precision_level) 249 { 250 GNUNET_assert (0 == 251 json_object_set_new (req_obj, 252 "unit_precision_level", 253 json_integer ( 254 *unit_precision_level))); 255 } 256 if (! unit_allow_fraction) 257 { 258 GNUNET_assert (0 == 259 json_object_del (req_obj, 260 "unit_allow_fraction")); 261 if (NULL != unit_precision_level) 262 GNUNET_assert (0 == 263 json_object_del (req_obj, 264 "unit_precision_level")); 265 } 266 pph = GNUNET_new (struct TALER_MERCHANT_ProductsPostHandle); 267 pph->ctx = ctx; 268 pph->cb = cb; 269 pph->cb_cls = cb_cls; 270 pph->url = TALER_url_join (backend_url, 271 "private/products", 272 NULL); 273 if (NULL == pph->url) 274 { 275 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 276 "Could not construct request URL.\n"); 277 json_decref (req_obj); 278 GNUNET_free (pph); 279 return NULL; 280 } 281 { 282 CURL *eh; 283 284 eh = TALER_MERCHANT_curl_easy_get_ (pph->url); 285 GNUNET_assert (GNUNET_OK == 286 TALER_curl_easy_post (&pph->post_ctx, 287 eh, 288 req_obj)); 289 json_decref (req_obj); 290 pph->job = GNUNET_CURL_job_add2 (ctx, 291 eh, 292 pph->post_ctx.headers, 293 &handle_post_products_finished, 294 pph); 295 GNUNET_assert (NULL != pph->job); 296 } 297 return pph; 298 } 299 300 301 struct TALER_MERCHANT_ProductsPostHandle * 302 TALER_MERCHANT_products_post3 ( 303 struct GNUNET_CURL_Context *ctx, 304 const char *backend_url, 305 const char *product_id, 306 const char *description, 307 const json_t *description_i18n, 308 const char *unit, 309 const struct TALER_Amount *price, 310 const char *image, 311 const json_t *taxes, 312 int64_t total_stock, 313 const json_t *address, 314 struct GNUNET_TIME_Timestamp next_restock, 315 uint32_t minimum_age, 316 unsigned int num_cats, 317 const uint64_t *cats, 318 TALER_MERCHANT_ProductsPostCallback cb, 319 void *cb_cls) 320 { 321 return TALER_MERCHANT_products_post4 (ctx, 322 backend_url, 323 product_id, 324 description, 325 description_i18n, 326 unit, 327 price, 328 1, 329 image, 330 taxes, 331 total_stock, 332 0, 333 false, 334 NULL, 335 address, 336 next_restock, 337 minimum_age, 338 num_cats, 339 cats, 340 cb, 341 cb_cls); 342 } 343 344 345 struct TALER_MERCHANT_ProductsPostHandle * 346 TALER_MERCHANT_products_post2 ( 347 struct GNUNET_CURL_Context *ctx, 348 const char *backend_url, 349 const char *product_id, 350 const char *description, 351 const json_t *description_i18n, 352 const char *unit, 353 const struct TALER_Amount *price, 354 const char *image, 355 const json_t *taxes, 356 int64_t total_stock, 357 const json_t *address, 358 struct GNUNET_TIME_Timestamp next_restock, 359 uint32_t minimum_age, 360 TALER_MERCHANT_ProductsPostCallback cb, 361 void *cb_cls) 362 { 363 return TALER_MERCHANT_products_post3 (ctx, 364 backend_url, 365 product_id, 366 description, 367 description_i18n, 368 unit, 369 price, 370 image, 371 taxes, 372 total_stock, 373 address, 374 next_restock, 375 minimum_age, 376 0, 377 NULL, 378 cb, 379 cb_cls); 380 } 381 382 383 struct TALER_MERCHANT_ProductsPostHandle * 384 TALER_MERCHANT_products_post ( 385 struct GNUNET_CURL_Context *ctx, 386 const char *backend_url, 387 const char *product_id, 388 const char *description, 389 const json_t *description_i18n, 390 const char *unit, 391 const struct TALER_Amount *price, 392 const char *image, 393 const json_t *taxes, 394 int64_t total_stock, 395 const json_t *address, 396 struct GNUNET_TIME_Timestamp next_restock, 397 TALER_MERCHANT_ProductsPostCallback cb, 398 void *cb_cls) 399 { 400 return TALER_MERCHANT_products_post2 (ctx, 401 backend_url, 402 product_id, 403 description, 404 description_i18n, 405 unit, 406 price, 407 image, 408 taxes, 409 total_stock, 410 address, 411 next_restock, 412 0, 413 cb, 414 cb_cls); 415 } 416 417 418 void 419 TALER_MERCHANT_products_post_cancel ( 420 struct TALER_MERCHANT_ProductsPostHandle *pph) 421 { 422 if (NULL != pph->job) 423 { 424 GNUNET_CURL_job_cancel (pph->job); 425 pph->job = NULL; 426 } 427 TALER_curl_easy_post_finished (&pph->post_ctx); 428 GNUNET_free (pph->url); 429 GNUNET_free (pph); 430 } 431 432 433 /* end of merchant_api_post_products.c */