merchant_api_get-private-products-PRODUCT_ID.c (9812B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, 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 Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file src/lib/merchant_api_get-private-products-PRODUCT_ID.c 19 * @brief Implementation of the GET /private/products/$PRODUCT_ID request 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include <curl/curl.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include <taler/merchant/get-private-products-PRODUCT_ID.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 #define MAX_CATEGORIES 1024 34 35 /** 36 * Handle for a GET /private/products/$PRODUCT_ID operation. 37 */ 38 struct TALER_MERCHANT_GetPrivateProductHandle 39 { 40 /** 41 * Base URL of the merchant backend. 42 */ 43 char *base_url; 44 45 /** 46 * Product identifier. 47 */ 48 char *product_id; 49 50 /** 51 * The full URL for this request. 52 */ 53 char *url; 54 55 /** 56 * Handle for the request. 57 */ 58 struct GNUNET_CURL_Job *job; 59 60 /** 61 * Function to call with the result. 62 */ 63 TALER_MERCHANT_GetPrivateProductCallback cb; 64 65 /** 66 * Closure for @a cb. 67 */ 68 TALER_MERCHANT_GET_PRIVATE_PRODUCT_RESULT_CLOSURE *cb_cls; 69 70 /** 71 * Reference to the execution context. 72 */ 73 struct GNUNET_CURL_Context *ctx; 74 }; 75 76 77 /** 78 * Function called when we're done processing the 79 * HTTP GET /private/products/$PRODUCT_ID request. 80 * 81 * @param cls the `struct TALER_MERCHANT_GetPrivateProductHandle` 82 * @param response_code HTTP response code, 0 on error 83 * @param response response body, NULL if not in JSON 84 */ 85 static void 86 handle_get_product_finished (void *cls, 87 long response_code, 88 const void *response) 89 { 90 struct TALER_MERCHANT_GetPrivateProductHandle *gpp = cls; 91 const json_t *json = response; 92 struct TALER_MERCHANT_GetPrivateProductResponse pgr = { 93 .hr.http_status = (unsigned int) response_code, 94 .hr.reply = json 95 }; 96 97 gpp->job = NULL; 98 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 99 "Got /private/products/$PRODUCT_ID response with status code %u\n", 100 (unsigned int) response_code); 101 switch (response_code) 102 { 103 case MHD_HTTP_OK: 104 { 105 const json_t *jcategories = NULL; 106 struct GNUNET_JSON_Specification spec[] = { 107 GNUNET_JSON_spec_string ( 108 "product_name", 109 &pgr.details.ok.product_name), 110 GNUNET_JSON_spec_string ( 111 "description", 112 &pgr.details.ok.description), 113 GNUNET_JSON_spec_object_const ( 114 "description_i18n", 115 &pgr.details.ok.description_i18n), 116 GNUNET_JSON_spec_string ( 117 "unit", 118 &pgr.details.ok.unit), 119 TALER_JSON_spec_amount_any_array ( 120 "unit_price", 121 &pgr.details.ok.unit_price_len, 122 (struct TALER_Amount **) &pgr.details.ok.unit_price), 123 TALER_JSON_spec_amount_any ( 124 "price", 125 &pgr.details.ok.price), 126 GNUNET_JSON_spec_mark_optional ( 127 GNUNET_JSON_spec_string ( 128 "image", 129 &pgr.details.ok.image), 130 NULL), 131 GNUNET_JSON_spec_mark_optional ( 132 GNUNET_JSON_spec_array_const ( 133 "taxes", 134 &pgr.details.ok.taxes), 135 NULL), 136 GNUNET_JSON_spec_int64 ( 137 "total_stock", 138 &pgr.details.ok.total_stock), 139 GNUNET_JSON_spec_string ( 140 "unit_total_stock", 141 &pgr.details.ok.unit_total_stock), 142 GNUNET_JSON_spec_bool ( 143 "unit_allow_fraction", 144 &pgr.details.ok.unit_allow_fraction), 145 GNUNET_JSON_spec_uint32 ( 146 "unit_precision_level", 147 &pgr.details.ok.unit_precision_level), 148 GNUNET_JSON_spec_uint64 ( 149 "total_sold", 150 &pgr.details.ok.total_sold), 151 GNUNET_JSON_spec_uint64 ( 152 "total_lost", 153 &pgr.details.ok.total_lost), 154 GNUNET_JSON_spec_mark_optional ( 155 GNUNET_JSON_spec_object_const ( 156 "address", 157 &pgr.details.ok.location), 158 NULL), 159 GNUNET_JSON_spec_mark_optional ( 160 GNUNET_JSON_spec_timestamp ( 161 "next_restock", 162 &pgr.details.ok.next_restock), 163 NULL), 164 GNUNET_JSON_spec_mark_optional ( 165 GNUNET_JSON_spec_array_const ( 166 "categories", 167 &jcategories), 168 NULL), 169 GNUNET_JSON_spec_mark_optional ( 170 GNUNET_JSON_spec_uint64 ( 171 "product_group_id", 172 &pgr.details.ok.product_group_id), 173 NULL), 174 GNUNET_JSON_spec_mark_optional ( 175 GNUNET_JSON_spec_uint64 ( 176 "money_pot_id", 177 &pgr.details.ok.money_pot_id), 178 NULL), 179 GNUNET_JSON_spec_mark_optional ( 180 GNUNET_JSON_spec_bool ( 181 "price_is_net", 182 &pgr.details.ok.price_is_net), 183 NULL), 184 GNUNET_JSON_spec_mark_optional ( 185 GNUNET_JSON_spec_uint16 ( 186 "minimum_age", 187 &pgr.details.ok.minimum_age), 188 NULL), 189 GNUNET_JSON_spec_end () 190 }; 191 192 if (GNUNET_OK != 193 GNUNET_JSON_parse (json, 194 spec, 195 NULL, NULL)) 196 { 197 pgr.hr.http_status = 0; 198 pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 199 break; 200 } 201 { 202 unsigned int num_categories; 203 uint64_t *categories; 204 205 num_categories = (unsigned int) json_array_size (jcategories); 206 if ( (json_array_size (jcategories) != (size_t) num_categories) || 207 (num_categories > MAX_CATEGORIES) ) 208 { 209 GNUNET_break_op (0); 210 pgr.hr.http_status = 0; 211 pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 212 break; 213 } 214 categories = GNUNET_new_array (num_categories, 215 uint64_t); 216 for (unsigned int i = 0; i < num_categories; i++) 217 { 218 json_t *jval = json_array_get (jcategories, 219 i); 220 221 if (! json_is_integer (jval)) 222 { 223 GNUNET_break_op (0); 224 GNUNET_free (categories); 225 pgr.hr.http_status = 0; 226 pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 227 goto done; 228 } 229 categories[i] = (uint64_t) json_integer_value (jval); 230 } 231 pgr.details.ok.categories_len = num_categories; 232 pgr.details.ok.categories = categories; 233 gpp->cb (gpp->cb_cls, 234 &pgr); 235 GNUNET_free (categories); 236 TALER_MERCHANT_get_private_product_cancel (gpp); 237 return; 238 } 239 } 240 case MHD_HTTP_UNAUTHORIZED: 241 pgr.hr.ec = TALER_JSON_get_error_code (json); 242 pgr.hr.hint = TALER_JSON_get_error_hint (json); 243 break; 244 case MHD_HTTP_NOT_FOUND: 245 pgr.hr.ec = TALER_JSON_get_error_code (json); 246 pgr.hr.hint = TALER_JSON_get_error_hint (json); 247 break; 248 default: 249 pgr.hr.ec = TALER_JSON_get_error_code (json); 250 pgr.hr.hint = TALER_JSON_get_error_hint (json); 251 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 252 "Unexpected response code %u/%d\n", 253 (unsigned int) response_code, 254 (int) pgr.hr.ec); 255 break; 256 } 257 done: 258 gpp->cb (gpp->cb_cls, 259 &pgr); 260 TALER_MERCHANT_get_private_product_cancel (gpp); 261 } 262 263 264 struct TALER_MERCHANT_GetPrivateProductHandle * 265 TALER_MERCHANT_get_private_product_create ( 266 struct GNUNET_CURL_Context *ctx, 267 const char *url, 268 const char *product_id) 269 { 270 struct TALER_MERCHANT_GetPrivateProductHandle *gpp; 271 272 gpp = GNUNET_new (struct TALER_MERCHANT_GetPrivateProductHandle); 273 gpp->ctx = ctx; 274 gpp->base_url = GNUNET_strdup (url); 275 gpp->product_id = GNUNET_strdup (product_id); 276 return gpp; 277 } 278 279 280 enum TALER_ErrorCode 281 TALER_MERCHANT_get_private_product_start ( 282 struct TALER_MERCHANT_GetPrivateProductHandle *gpp, 283 TALER_MERCHANT_GetPrivateProductCallback cb, 284 TALER_MERCHANT_GET_PRIVATE_PRODUCT_RESULT_CLOSURE *cb_cls) 285 { 286 CURL *eh; 287 288 gpp->cb = cb; 289 gpp->cb_cls = cb_cls; 290 { 291 char *path; 292 293 GNUNET_asprintf (&path, 294 "private/products/%s", 295 gpp->product_id); 296 gpp->url = TALER_url_join (gpp->base_url, 297 path, 298 NULL); 299 GNUNET_free (path); 300 } 301 if (NULL == gpp->url) 302 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 303 eh = TALER_MERCHANT_curl_easy_get_ (gpp->url); 304 if (NULL == eh) 305 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 306 gpp->job = GNUNET_CURL_job_add (gpp->ctx, 307 eh, 308 &handle_get_product_finished, 309 gpp); 310 if (NULL == gpp->job) 311 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 312 return TALER_EC_NONE; 313 } 314 315 316 void 317 TALER_MERCHANT_get_private_product_cancel ( 318 struct TALER_MERCHANT_GetPrivateProductHandle *gpp) 319 { 320 if (NULL != gpp->job) 321 { 322 GNUNET_CURL_job_cancel (gpp->job); 323 gpp->job = NULL; 324 } 325 GNUNET_free (gpp->url); 326 GNUNET_free (gpp->product_id); 327 GNUNET_free (gpp->base_url); 328 GNUNET_free (gpp); 329 } 330 331 332 /* end of merchant_api_get-private-products-PRODUCT_ID-new.c */