Line data Source code
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 18 : handle_post_products_finished (void *cls,
86 : long response_code,
87 : const void *response)
88 : {
89 18 : struct TALER_MERCHANT_ProductsPostHandle *pph = cls;
90 18 : const json_t *json = response;
91 18 : struct TALER_MERCHANT_HttpResponse hr = {
92 18 : .http_status = (unsigned int) response_code,
93 : .reply = json
94 : };
95 :
96 18 : pph->job = NULL;
97 18 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
98 : "POST /products completed with response code %u\n",
99 : (unsigned int) response_code);
100 18 : switch (response_code)
101 : {
102 0 : case 0:
103 0 : hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
104 0 : break;
105 16 : case MHD_HTTP_NO_CONTENT:
106 16 : break;
107 0 : case MHD_HTTP_BAD_REQUEST:
108 0 : hr.ec = TALER_JSON_get_error_code (json);
109 0 : 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 0 : break;
114 0 : case MHD_HTTP_UNAUTHORIZED:
115 0 : hr.ec = TALER_JSON_get_error_code (json);
116 0 : hr.hint = TALER_JSON_get_error_hint (json);
117 : /* Nothing really to verify, merchant says we need to authenticate. */
118 0 : break;
119 0 : case MHD_HTTP_FORBIDDEN:
120 0 : hr.ec = TALER_JSON_get_error_code (json);
121 0 : 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 0 : break;
126 0 : case MHD_HTTP_NOT_FOUND:
127 0 : hr.ec = TALER_JSON_get_error_code (json);
128 0 : 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 0 : break;
133 2 : case MHD_HTTP_CONFLICT:
134 2 : hr.ec = TALER_JSON_get_error_code (json);
135 2 : hr.hint = TALER_JSON_get_error_hint (json);
136 2 : break;
137 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
138 0 : hr.ec = TALER_JSON_get_error_code (json);
139 0 : 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 0 : break;
143 0 : default:
144 0 : TALER_MERCHANT_parse_error_details_ (json,
145 : response_code,
146 : &hr);
147 : /* unexpected response code */
148 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
149 : "Unexpected response code %u/%d\n",
150 : (unsigned int) response_code,
151 : (int) hr.ec);
152 0 : GNUNET_break_op (0);
153 0 : break;
154 : }
155 18 : pph->cb (pph->cb_cls,
156 : &hr);
157 18 : TALER_MERCHANT_products_post_cancel (pph);
158 18 : }
159 :
160 :
161 : struct TALER_MERCHANT_ProductsPostHandle *
162 18 : 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 18 : TALER_MERCHANT_format_stock_string (total_stock,
191 : total_stock_frac,
192 : unit_total_stock_buf,
193 : sizeof (unit_total_stock_buf));
194 :
195 18 : if (0 == num_cats)
196 : {
197 18 : categories = NULL;
198 : }
199 : else
200 : {
201 0 : categories = json_array ();
202 0 : GNUNET_assert (NULL != categories);
203 0 : for (unsigned int i = 0; i<num_cats; i++)
204 0 : GNUNET_assert (0 ==
205 : json_array_append_new (categories,
206 : json_integer (cats[i])));
207 : }
208 : {
209 18 : 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 18 : if (NULL != unit_precision_level)
249 : {
250 2 : GNUNET_assert (0 ==
251 : json_object_set_new (req_obj,
252 : "unit_precision_level",
253 : json_integer (
254 : *unit_precision_level)));
255 : }
256 18 : if (! unit_allow_fraction)
257 : {
258 16 : GNUNET_assert (0 ==
259 : json_object_del (req_obj,
260 : "unit_allow_fraction"));
261 16 : if (NULL != unit_precision_level)
262 0 : GNUNET_assert (0 ==
263 : json_object_del (req_obj,
264 : "unit_precision_level"));
265 : }
266 18 : pph = GNUNET_new (struct TALER_MERCHANT_ProductsPostHandle);
267 18 : pph->ctx = ctx;
268 18 : pph->cb = cb;
269 18 : pph->cb_cls = cb_cls;
270 18 : pph->url = TALER_url_join (backend_url,
271 : "private/products",
272 : NULL);
273 18 : if (NULL == pph->url)
274 : {
275 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
276 : "Could not construct request URL.\n");
277 0 : json_decref (req_obj);
278 0 : GNUNET_free (pph);
279 0 : return NULL;
280 : }
281 : {
282 : CURL *eh;
283 :
284 18 : eh = TALER_MERCHANT_curl_easy_get_ (pph->url);
285 18 : GNUNET_assert (GNUNET_OK ==
286 : TALER_curl_easy_post (&pph->post_ctx,
287 : eh,
288 : req_obj));
289 18 : json_decref (req_obj);
290 36 : pph->job = GNUNET_CURL_job_add2 (ctx,
291 : eh,
292 18 : pph->post_ctx.headers,
293 : &handle_post_products_finished,
294 : pph);
295 18 : GNUNET_assert (NULL != pph->job);
296 : }
297 18 : return pph;
298 : }
299 :
300 :
301 : struct TALER_MERCHANT_ProductsPostHandle *
302 16 : 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 16 : 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 16 : 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 16 : 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 0 : 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 0 : 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 18 : TALER_MERCHANT_products_post_cancel (
420 : struct TALER_MERCHANT_ProductsPostHandle *pph)
421 : {
422 18 : if (NULL != pph->job)
423 : {
424 0 : GNUNET_CURL_job_cancel (pph->job);
425 0 : pph->job = NULL;
426 : }
427 18 : TALER_curl_easy_post_finished (&pph->post_ctx);
428 18 : GNUNET_free (pph->url);
429 18 : GNUNET_free (pph);
430 18 : }
431 :
432 :
433 : /* end of merchant_api_post_products.c */
|