merchant_api_patch-private-units-UNIT.c (10909B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025-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 7 Software 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 A 11 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 <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file merchant_api_patch-private-units-UNIT-new.c 18 * @brief Implementation of the PATCH /private/units/$UNIT request 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include <curl/curl.h> 23 #include <jansson.h> 24 #include <microhttpd.h> /* just for HTTP status codes */ 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_curl_lib.h> 27 #include <taler/taler-merchant/patch-private-units-UNIT.h> 28 #include "merchant_api_curl_defaults.h" 29 #include "merchant_api_common.h" 30 #include <taler/taler_json_lib.h> 31 #include <taler/taler_curl_lib.h> 32 33 34 /** 35 * Handle for a PATCH /private/units/$UNIT operation. 36 */ 37 struct TALER_MERCHANT_PatchPrivateUnitHandle 38 { 39 /** 40 * Base URL of the merchant backend. 41 */ 42 char *base_url; 43 44 /** 45 * The full URL for this request. 46 */ 47 char *url; 48 49 /** 50 * Handle for the request. 51 */ 52 struct GNUNET_CURL_Job *job; 53 54 /** 55 * Function to call with the result. 56 */ 57 TALER_MERCHANT_PatchPrivateUnitCallback cb; 58 59 /** 60 * Closure for @a cb. 61 */ 62 TALER_MERCHANT_PATCH_PRIVATE_UNIT_RESULT_CLOSURE *cb_cls; 63 64 /** 65 * Reference to the execution context. 66 */ 67 struct GNUNET_CURL_Context *ctx; 68 69 /** 70 * Minor context that holds body and headers. 71 */ 72 struct TALER_CURL_PostContext post_ctx; 73 74 /** 75 * Identifier of the unit to update. 76 */ 77 char *unit_id; 78 79 /** 80 * Long name of the unit (optional). 81 */ 82 const char *unit_name_long; 83 84 /** 85 * Short name (symbol) of the unit (optional). 86 */ 87 const char *unit_name_short; 88 89 /** 90 * Internationalized long names (JSON, optional). 91 */ 92 const json_t *unit_name_long_i18n; 93 94 /** 95 * Internationalized short names (JSON, optional). 96 */ 97 const json_t *unit_name_short_i18n; 98 99 /** 100 * Whether fractional quantities are allowed. 101 */ 102 bool unit_allow_fraction; 103 104 /** 105 * Whether unit_allow_fraction was explicitly set. 106 */ 107 bool have_unit_allow_fraction; 108 109 /** 110 * Precision level for fractions. 111 */ 112 uint32_t unit_precision_level; 113 114 /** 115 * Whether unit_precision_level was explicitly set. 116 */ 117 bool have_unit_precision_level; 118 119 /** 120 * Active status of the unit. 121 */ 122 bool unit_active; 123 124 /** 125 * Whether unit_active was explicitly set. 126 */ 127 bool have_unit_active; 128 }; 129 130 131 /** 132 * Function called when we're done processing the 133 * HTTP PATCH /private/units/$UNIT request. 134 * 135 * @param cls the `struct TALER_MERCHANT_PatchPrivateUnitHandle` 136 * @param response_code HTTP response code, 0 on error 137 * @param response response body, NULL if not in JSON 138 */ 139 static void 140 handle_patch_unit_finished (void *cls, 141 long response_code, 142 const void *response) 143 { 144 struct TALER_MERCHANT_PatchPrivateUnitHandle *uph = cls; 145 const json_t *json = response; 146 struct TALER_MERCHANT_PatchPrivateUnitResponse upr = { 147 .hr.http_status = (unsigned int) response_code, 148 .hr.reply = json 149 }; 150 151 uph->job = NULL; 152 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 153 "PATCH /private/units/$UNIT completed with status %u\n", 154 (unsigned int) response_code); 155 switch (response_code) 156 { 157 case MHD_HTTP_NO_CONTENT: 158 break; 159 case MHD_HTTP_BAD_REQUEST: 160 case MHD_HTTP_UNAUTHORIZED: 161 case MHD_HTTP_FORBIDDEN: 162 case MHD_HTTP_NOT_FOUND: 163 case MHD_HTTP_CONFLICT: 164 case MHD_HTTP_INTERNAL_SERVER_ERROR: 165 upr.hr.ec = TALER_JSON_get_error_code (json); 166 upr.hr.hint = TALER_JSON_get_error_hint (json); 167 break; 168 case 0: 169 upr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 170 break; 171 default: 172 TALER_MERCHANT_parse_error_details_ (json, 173 response_code, 174 &upr.hr); 175 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 176 "Unexpected response %u/%d for PATCH /private/units\n", 177 (unsigned int) response_code, 178 (int) upr.hr.ec); 179 GNUNET_break_op (0); 180 break; 181 } 182 uph->cb (uph->cb_cls, 183 &upr); 184 TALER_MERCHANT_patch_private_unit_cancel (uph); 185 } 186 187 188 struct TALER_MERCHANT_PatchPrivateUnitHandle * 189 TALER_MERCHANT_patch_private_unit_create ( 190 struct GNUNET_CURL_Context *ctx, 191 const char *url, 192 const char *unit_id) 193 { 194 struct TALER_MERCHANT_PatchPrivateUnitHandle *uph; 195 196 uph = GNUNET_new (struct TALER_MERCHANT_PatchPrivateUnitHandle); 197 uph->ctx = ctx; 198 uph->base_url = GNUNET_strdup (url); 199 uph->unit_id = GNUNET_strdup (unit_id); 200 return uph; 201 } 202 203 204 enum GNUNET_GenericReturnValue 205 TALER_MERCHANT_patch_private_unit_set_options_ ( 206 struct TALER_MERCHANT_PatchPrivateUnitHandle *uph, 207 unsigned int num_options, 208 const struct TALER_MERCHANT_PatchPrivateUnitOptionValue *options) 209 { 210 for (unsigned int i = 0; i < num_options; i++) 211 { 212 switch (options[i].option) 213 { 214 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_END: 215 return GNUNET_OK; 216 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_LONG: 217 uph->unit_name_long = options[i].details.unit_name_long; 218 break; 219 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_SHORT: 220 uph->unit_name_short = options[i].details.unit_name_short; 221 break; 222 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_LONG_I18N: 223 uph->unit_name_long_i18n = options[i].details.unit_name_long_i18n; 224 break; 225 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_SHORT_I18N: 226 uph->unit_name_short_i18n = options[i].details.unit_name_short_i18n; 227 break; 228 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_ALLOW_FRACTION: 229 uph->unit_allow_fraction = options[i].details.unit_allow_fraction; 230 uph->have_unit_allow_fraction = true; 231 break; 232 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_PRECISION_LEVEL: 233 uph->unit_precision_level = options[i].details.unit_precision_level; 234 uph->have_unit_precision_level = true; 235 break; 236 case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_ACTIVE: 237 uph->unit_active = options[i].details.unit_active; 238 uph->have_unit_active = true; 239 break; 240 default: 241 GNUNET_break (0); 242 return GNUNET_SYSERR; 243 } 244 } 245 return GNUNET_OK; 246 } 247 248 249 enum TALER_ErrorCode 250 TALER_MERCHANT_patch_private_unit_start ( 251 struct TALER_MERCHANT_PatchPrivateUnitHandle *uph, 252 TALER_MERCHANT_PatchPrivateUnitCallback cb, 253 TALER_MERCHANT_PATCH_PRIVATE_UNIT_RESULT_CLOSURE *cb_cls) 254 { 255 json_t *req_obj; 256 CURL *eh; 257 258 uph->cb = cb; 259 uph->cb_cls = cb_cls; 260 { 261 char *path; 262 263 GNUNET_asprintf (&path, 264 "private/units/%s", 265 uph->unit_id); 266 uph->url = TALER_url_join (uph->base_url, 267 path, 268 NULL); 269 GNUNET_free (path); 270 } 271 if (NULL == uph->url) 272 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 273 274 /* Build JSON request body from options that were set */ 275 req_obj = json_object (); 276 if (NULL == req_obj) 277 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 278 if (NULL != uph->unit_name_long) 279 { 280 GNUNET_assert (0 == 281 json_object_set_new (req_obj, 282 "unit_name_long", 283 json_string (uph->unit_name_long))); 284 } 285 if (NULL != uph->unit_name_short) 286 { 287 GNUNET_assert (0 == 288 json_object_set_new (req_obj, 289 "unit_name_short", 290 json_string (uph->unit_name_short))); 291 } 292 if (NULL != uph->unit_name_long_i18n) 293 { 294 GNUNET_assert (0 == 295 json_object_set_new ( 296 req_obj, 297 "unit_name_long_i18n", 298 json_incref ((json_t *) uph->unit_name_long_i18n))); 299 } 300 if (NULL != uph->unit_name_short_i18n) 301 { 302 GNUNET_assert (0 == 303 json_object_set_new ( 304 req_obj, 305 "unit_name_short_i18n", 306 json_incref ((json_t *) uph->unit_name_short_i18n))); 307 } 308 if (uph->have_unit_allow_fraction) 309 { 310 GNUNET_assert (0 == 311 json_object_set_new (req_obj, 312 "unit_allow_fraction", 313 json_boolean ( 314 uph->unit_allow_fraction))); 315 } 316 if (uph->have_unit_precision_level) 317 { 318 GNUNET_assert (0 == 319 json_object_set_new (req_obj, 320 "unit_precision_level", 321 json_integer ( 322 (json_int_t) 323 uph->unit_precision_level))); 324 } 325 if (uph->have_unit_active) 326 { 327 GNUNET_assert (0 == 328 json_object_set_new (req_obj, 329 "unit_active", 330 json_boolean (uph->unit_active))); 331 } 332 eh = TALER_MERCHANT_curl_easy_get_ (uph->url); 333 if ( (NULL == eh) || 334 (GNUNET_OK != 335 TALER_curl_easy_post (&uph->post_ctx, 336 eh, 337 req_obj)) ) 338 { 339 GNUNET_break (0); 340 json_decref (req_obj); 341 if (NULL != eh) 342 curl_easy_cleanup (eh); 343 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 344 } 345 json_decref (req_obj); 346 GNUNET_assert (CURLE_OK == 347 curl_easy_setopt (eh, 348 CURLOPT_CUSTOMREQUEST, 349 MHD_HTTP_METHOD_PATCH)); 350 uph->job = GNUNET_CURL_job_add2 (uph->ctx, 351 eh, 352 uph->post_ctx.headers, 353 &handle_patch_unit_finished, 354 uph); 355 if (NULL == uph->job) 356 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 357 return TALER_EC_NONE; 358 } 359 360 361 void 362 TALER_MERCHANT_patch_private_unit_cancel ( 363 struct TALER_MERCHANT_PatchPrivateUnitHandle *uph) 364 { 365 if (NULL != uph->job) 366 { 367 GNUNET_CURL_job_cancel (uph->job); 368 uph->job = NULL; 369 } 370 TALER_curl_easy_post_finished (&uph->post_ctx); 371 GNUNET_free (uph->url); 372 GNUNET_free (uph->base_url); 373 GNUNET_free (uph->unit_id); 374 GNUNET_free (uph); 375 } 376 377 378 /* end of merchant_api_patch-private-units-UNIT-new.c */