Line data Source code
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/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 0 : handle_patch_unit_finished (void *cls,
141 : long response_code,
142 : const void *response)
143 : {
144 0 : struct TALER_MERCHANT_PatchPrivateUnitHandle *uph = cls;
145 0 : const json_t *json = response;
146 0 : struct TALER_MERCHANT_PatchPrivateUnitResponse upr = {
147 0 : .hr.http_status = (unsigned int) response_code,
148 : .hr.reply = json
149 : };
150 :
151 0 : uph->job = NULL;
152 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
153 : "PATCH /private/units/$UNIT completed with status %u\n",
154 : (unsigned int) response_code);
155 0 : switch (response_code)
156 : {
157 0 : case MHD_HTTP_NO_CONTENT:
158 0 : break;
159 0 : 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 0 : upr.hr.ec = TALER_JSON_get_error_code (json);
166 0 : upr.hr.hint = TALER_JSON_get_error_hint (json);
167 0 : break;
168 0 : case 0:
169 0 : upr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
170 0 : break;
171 0 : default:
172 0 : TALER_MERCHANT_parse_error_details_ (json,
173 : response_code,
174 : &upr.hr);
175 0 : 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 0 : GNUNET_break_op (0);
180 0 : break;
181 : }
182 0 : uph->cb (uph->cb_cls,
183 : &upr);
184 0 : TALER_MERCHANT_patch_private_unit_cancel (uph);
185 0 : }
186 :
187 :
188 : struct TALER_MERCHANT_PatchPrivateUnitHandle *
189 0 : 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 0 : uph = GNUNET_new (struct TALER_MERCHANT_PatchPrivateUnitHandle);
197 0 : uph->ctx = ctx;
198 0 : uph->base_url = GNUNET_strdup (url);
199 0 : uph->unit_id = GNUNET_strdup (unit_id);
200 0 : return uph;
201 : }
202 :
203 :
204 : enum GNUNET_GenericReturnValue
205 0 : 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 0 : for (unsigned int i = 0; i < num_options; i++)
211 : {
212 0 : switch (options[i].option)
213 : {
214 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_END:
215 0 : return GNUNET_OK;
216 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_LONG:
217 0 : uph->unit_name_long = options[i].details.unit_name_long;
218 0 : break;
219 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_SHORT:
220 0 : uph->unit_name_short = options[i].details.unit_name_short;
221 0 : break;
222 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_LONG_I18N:
223 0 : uph->unit_name_long_i18n = options[i].details.unit_name_long_i18n;
224 0 : break;
225 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_NAME_SHORT_I18N:
226 0 : uph->unit_name_short_i18n = options[i].details.unit_name_short_i18n;
227 0 : break;
228 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_ALLOW_FRACTION:
229 0 : uph->unit_allow_fraction = options[i].details.unit_allow_fraction;
230 0 : uph->have_unit_allow_fraction = true;
231 0 : break;
232 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_PRECISION_LEVEL:
233 0 : uph->unit_precision_level = options[i].details.unit_precision_level;
234 0 : uph->have_unit_precision_level = true;
235 0 : break;
236 0 : case TALER_MERCHANT_PATCH_PRIVATE_UNIT_OPTION_UNIT_ACTIVE:
237 0 : uph->unit_active = options[i].details.unit_active;
238 0 : uph->have_unit_active = true;
239 0 : break;
240 0 : default:
241 0 : GNUNET_break (0);
242 0 : return GNUNET_SYSERR;
243 : }
244 : }
245 0 : return GNUNET_OK;
246 : }
247 :
248 :
249 : enum TALER_ErrorCode
250 0 : 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 0 : uph->cb = cb;
259 0 : uph->cb_cls = cb_cls;
260 : {
261 : char *path;
262 :
263 0 : GNUNET_asprintf (&path,
264 : "private/units/%s",
265 : uph->unit_id);
266 0 : uph->url = TALER_url_join (uph->base_url,
267 : path,
268 : NULL);
269 0 : GNUNET_free (path);
270 : }
271 0 : if (NULL == uph->url)
272 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
273 :
274 : /* Build JSON request body from options that were set */
275 0 : req_obj = json_object ();
276 0 : if (NULL == req_obj)
277 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
278 0 : if (NULL != uph->unit_name_long)
279 : {
280 0 : GNUNET_assert (0 ==
281 : json_object_set_new (req_obj,
282 : "unit_name_long",
283 : json_string (uph->unit_name_long)));
284 : }
285 0 : if (NULL != uph->unit_name_short)
286 : {
287 0 : GNUNET_assert (0 ==
288 : json_object_set_new (req_obj,
289 : "unit_name_short",
290 : json_string (uph->unit_name_short)));
291 : }
292 0 : if (NULL != uph->unit_name_long_i18n)
293 : {
294 0 : 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 0 : if (NULL != uph->unit_name_short_i18n)
301 : {
302 0 : 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 0 : if (uph->have_unit_allow_fraction)
309 : {
310 0 : GNUNET_assert (0 ==
311 : json_object_set_new (req_obj,
312 : "unit_allow_fraction",
313 : json_boolean (
314 : uph->unit_allow_fraction)));
315 : }
316 0 : if (uph->have_unit_precision_level)
317 : {
318 0 : 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 0 : if (uph->have_unit_active)
326 : {
327 0 : GNUNET_assert (0 ==
328 : json_object_set_new (req_obj,
329 : "unit_active",
330 : json_boolean (uph->unit_active)));
331 : }
332 0 : eh = TALER_MERCHANT_curl_easy_get_ (uph->url);
333 0 : if ( (NULL == eh) ||
334 : (GNUNET_OK !=
335 0 : TALER_curl_easy_post (&uph->post_ctx,
336 : eh,
337 : req_obj)) )
338 : {
339 0 : GNUNET_break (0);
340 0 : json_decref (req_obj);
341 0 : if (NULL != eh)
342 0 : curl_easy_cleanup (eh);
343 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
344 : }
345 0 : json_decref (req_obj);
346 0 : GNUNET_assert (CURLE_OK ==
347 : curl_easy_setopt (eh,
348 : CURLOPT_CUSTOMREQUEST,
349 : MHD_HTTP_METHOD_PATCH));
350 0 : uph->job = GNUNET_CURL_job_add2 (uph->ctx,
351 : eh,
352 0 : uph->post_ctx.headers,
353 : &handle_patch_unit_finished,
354 : uph);
355 0 : if (NULL == uph->job)
356 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
357 0 : return TALER_EC_NONE;
358 : }
359 :
360 :
361 : void
362 0 : TALER_MERCHANT_patch_private_unit_cancel (
363 : struct TALER_MERCHANT_PatchPrivateUnitHandle *uph)
364 : {
365 0 : if (NULL != uph->job)
366 : {
367 0 : GNUNET_CURL_job_cancel (uph->job);
368 0 : uph->job = NULL;
369 : }
370 0 : TALER_curl_easy_post_finished (&uph->post_ctx);
371 0 : GNUNET_free (uph->url);
372 0 : GNUNET_free (uph->base_url);
373 0 : GNUNET_free (uph->unit_id);
374 0 : GNUNET_free (uph);
375 0 : }
376 :
377 :
378 : /* end of merchant_api_patch-private-units-UNIT-new.c */
|