Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020-2025 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Affero General Public License as
7 : published by the Free Software Foundation; either version 3,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 :
20 : /**
21 : * @file taler-merchant-httpd_private-post-products.c
22 : * @brief implementing POST /products request handling
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd_private-post-products.h"
27 : #include "taler-merchant-httpd_helper.h"
28 : #include <taler/taler_json_lib.h>
29 :
30 : MHD_RESULT
31 22 : TMH_private_post_products (const struct TMH_RequestHandler *rh,
32 : struct MHD_Connection *connection,
33 : struct TMH_HandlerContext *hc)
34 : {
35 22 : struct TMH_MerchantInstance *mi = hc->instance;
36 22 : struct TALER_MERCHANTDB_ProductDetails pd = { 0 };
37 22 : const json_t *categories = NULL;
38 : const char *product_id;
39 : int64_t total_stock;
40 22 : const char *unit_total_stock = NULL;
41 : bool unit_total_stock_missing;
42 : bool total_stock_missing;
43 : bool unit_price_missing;
44 : bool unit_allow_fraction;
45 : bool unit_allow_fraction_missing;
46 : uint32_t unit_precision_level;
47 : bool unit_precision_missing;
48 : struct TALER_Amount price;
49 : bool price_missing;
50 : struct GNUNET_JSON_Specification spec[] = {
51 22 : GNUNET_JSON_spec_string ("product_id",
52 : &product_id),
53 : /* new in protocol v20, thus optional for backwards-compatibility */
54 22 : GNUNET_JSON_spec_mark_optional (
55 : GNUNET_JSON_spec_string ("product_name",
56 : (const char **) &pd.product_name),
57 : NULL),
58 22 : GNUNET_JSON_spec_string ("description",
59 : (const char **) &pd.description),
60 22 : GNUNET_JSON_spec_mark_optional (
61 : GNUNET_JSON_spec_json ("description_i18n",
62 : &pd.description_i18n),
63 : NULL),
64 22 : GNUNET_JSON_spec_string ("unit",
65 : (const char **) &pd.unit),
66 22 : GNUNET_JSON_spec_mark_optional (
67 : TALER_JSON_spec_amount_any ("price",
68 : &price),
69 : &price_missing),
70 22 : GNUNET_JSON_spec_mark_optional (
71 : GNUNET_JSON_spec_string ("image",
72 : (const char **) &pd.image),
73 : NULL),
74 22 : GNUNET_JSON_spec_mark_optional (
75 : GNUNET_JSON_spec_json ("taxes",
76 : &pd.taxes),
77 : NULL),
78 22 : GNUNET_JSON_spec_mark_optional (
79 : GNUNET_JSON_spec_array_const ("categories",
80 : &categories),
81 : NULL),
82 22 : GNUNET_JSON_spec_mark_optional (
83 : GNUNET_JSON_spec_string ("unit_total_stock",
84 : &unit_total_stock),
85 : &unit_total_stock_missing),
86 22 : GNUNET_JSON_spec_mark_optional (
87 : GNUNET_JSON_spec_int64 ("total_stock",
88 : &total_stock),
89 : &total_stock_missing),
90 22 : GNUNET_JSON_spec_mark_optional (
91 : GNUNET_JSON_spec_bool ("unit_allow_fraction",
92 : &unit_allow_fraction),
93 : &unit_allow_fraction_missing),
94 22 : GNUNET_JSON_spec_mark_optional (
95 : GNUNET_JSON_spec_uint32 ("unit_precision_level",
96 : &unit_precision_level),
97 : &unit_precision_missing),
98 22 : GNUNET_JSON_spec_mark_optional (
99 : TALER_JSON_spec_amount_any_array ("unit_price",
100 : &pd.price_array_length,
101 : &pd.price_array),
102 : &unit_price_missing),
103 22 : GNUNET_JSON_spec_mark_optional (
104 : GNUNET_JSON_spec_json ("address",
105 : &pd.address),
106 : NULL),
107 22 : GNUNET_JSON_spec_mark_optional (
108 : GNUNET_JSON_spec_timestamp ("next_restock",
109 : &pd.next_restock),
110 : NULL),
111 22 : GNUNET_JSON_spec_mark_optional (
112 : GNUNET_JSON_spec_uint32 ("minimum_age",
113 : &pd.minimum_age),
114 : NULL),
115 22 : GNUNET_JSON_spec_mark_optional (
116 : GNUNET_JSON_spec_uint64 ("money_pot_id",
117 : &pd.money_pot_id),
118 : NULL),
119 22 : GNUNET_JSON_spec_mark_optional (
120 : GNUNET_JSON_spec_uint64 ("product_group_id",
121 : &pd.product_group_id),
122 : NULL),
123 22 : GNUNET_JSON_spec_mark_optional (
124 : GNUNET_JSON_spec_bool ("price_is_net",
125 : &pd.price_is_net),
126 : NULL),
127 22 : GNUNET_JSON_spec_end ()
128 : };
129 22 : size_t num_cats = 0;
130 22 : uint64_t *cats = NULL;
131 : bool conflict;
132 : bool no_instance;
133 : ssize_t no_cat;
134 : bool no_group;
135 : bool no_pot;
136 : enum GNUNET_DB_QueryStatus qs;
137 : MHD_RESULT ret;
138 :
139 22 : GNUNET_assert (NULL != mi);
140 : {
141 : enum GNUNET_GenericReturnValue res;
142 :
143 22 : res = TALER_MHD_parse_json_data (connection,
144 22 : hc->request_body,
145 : spec);
146 22 : if (GNUNET_OK != res)
147 : {
148 0 : GNUNET_break_op (0);
149 : return (GNUNET_NO == res)
150 : ? MHD_YES
151 0 : : MHD_NO;
152 : }
153 : /* For pre-v20 clients, we use the description given as the
154 : product name; remove once we make product_name mandatory. */
155 22 : if (NULL == pd.product_name)
156 2 : pd.product_name = pd.description;
157 :
158 22 : if (! unit_price_missing)
159 : {
160 22 : if (! price_missing)
161 : {
162 0 : if (0 != TALER_amount_cmp (&price,
163 0 : &pd.price_array[0]))
164 : {
165 0 : ret = TALER_MHD_reply_with_error (connection,
166 : MHD_HTTP_BAD_REQUEST,
167 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
168 : "price,unit_price mismatch");
169 0 : goto cleanup;
170 : }
171 : }
172 : }
173 : else
174 : {
175 0 : if (price_missing)
176 : {
177 0 : ret = TALER_MHD_reply_with_error (connection,
178 : MHD_HTTP_BAD_REQUEST,
179 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
180 : "price and unit_price missing");
181 0 : goto cleanup;
182 : }
183 0 : pd.price_array = GNUNET_new_array (1,
184 : struct TALER_Amount);
185 0 : pd.price_array[0] = price;
186 0 : pd.price_array_length = 1;
187 : }
188 : }
189 22 : if (! unit_precision_missing)
190 : {
191 2 : if (unit_precision_level > TMH_MAX_FRACTIONAL_PRECISION_LEVEL)
192 : {
193 0 : ret = TALER_MHD_reply_with_error (connection,
194 : MHD_HTTP_BAD_REQUEST,
195 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
196 : "unit_precision_level");
197 0 : goto cleanup;
198 : }
199 : }
200 : {
201 : bool default_allow_fractional;
202 : uint32_t default_precision_level;
203 :
204 22 : if (GNUNET_OK !=
205 22 : TMH_unit_defaults_for_instance (mi,
206 22 : pd.unit,
207 : &default_allow_fractional,
208 : &default_precision_level))
209 : {
210 0 : GNUNET_break (0);
211 0 : ret = TALER_MHD_reply_with_error (connection,
212 : MHD_HTTP_INTERNAL_SERVER_ERROR,
213 : TALER_EC_GENERIC_DB_FETCH_FAILED,
214 : "unit defaults");
215 0 : goto cleanup;
216 : }
217 22 : if (unit_allow_fraction_missing)
218 20 : unit_allow_fraction = default_allow_fractional;
219 22 : if (unit_precision_missing)
220 20 : unit_precision_level = default_precision_level;
221 : }
222 22 : if (! unit_allow_fraction)
223 18 : unit_precision_level = 0;
224 22 : pd.fractional_precision_level = unit_precision_level;
225 : {
226 : const char *eparam;
227 22 : if (GNUNET_OK !=
228 22 : TALER_MERCHANT_vk_process_quantity_inputs (
229 : TALER_MERCHANT_VK_STOCK,
230 : unit_allow_fraction,
231 : total_stock_missing,
232 : total_stock,
233 : unit_total_stock_missing,
234 : unit_total_stock,
235 : &pd.total_stock,
236 : &pd.total_stock_frac,
237 : &eparam))
238 : {
239 0 : ret = TALER_MHD_reply_with_error (
240 : connection,
241 : MHD_HTTP_BAD_REQUEST,
242 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
243 : eparam);
244 0 : goto cleanup;
245 : }
246 22 : pd.allow_fractional_quantity = unit_allow_fraction;
247 : }
248 22 : num_cats = json_array_size (categories);
249 22 : cats = GNUNET_new_array (num_cats,
250 : uint64_t);
251 : {
252 : size_t idx;
253 : json_t *val;
254 :
255 22 : json_array_foreach (categories, idx, val)
256 : {
257 0 : if (! json_is_integer (val))
258 : {
259 0 : GNUNET_break_op (0);
260 0 : ret = TALER_MHD_reply_with_error (connection,
261 : MHD_HTTP_BAD_REQUEST,
262 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
263 : "categories");
264 0 : goto cleanup;
265 : }
266 0 : cats[idx] = json_integer_value (val);
267 : }
268 : }
269 :
270 22 : if (NULL == pd.address)
271 6 : pd.address = json_object ();
272 22 : if (NULL == pd.description_i18n)
273 4 : pd.description_i18n = json_object ();
274 22 : if (NULL == pd.taxes)
275 2 : pd.taxes = json_array ();
276 :
277 : /* check taxes is well-formed */
278 22 : if (! TALER_MERCHANT_taxes_array_valid (pd.taxes))
279 : {
280 0 : GNUNET_break_op (0);
281 0 : ret = TALER_MHD_reply_with_error (connection,
282 : MHD_HTTP_BAD_REQUEST,
283 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
284 : "taxes");
285 0 : goto cleanup;
286 : }
287 :
288 22 : if (! TMH_location_object_valid (pd.address))
289 : {
290 0 : GNUNET_break_op (0);
291 0 : ret = TALER_MHD_reply_with_error (connection,
292 : MHD_HTTP_BAD_REQUEST,
293 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
294 : "address");
295 0 : goto cleanup;
296 : }
297 :
298 22 : if (! TALER_JSON_check_i18n (pd.description_i18n))
299 : {
300 0 : GNUNET_break_op (0);
301 0 : ret = TALER_MHD_reply_with_error (connection,
302 : MHD_HTTP_BAD_REQUEST,
303 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
304 : "description_i18n");
305 0 : goto cleanup;
306 : }
307 :
308 22 : if (NULL == pd.image)
309 0 : pd.image = (char *) "";
310 22 : if (! TALER_MERCHANT_image_data_url_valid (pd.image))
311 : {
312 0 : GNUNET_break_op (0);
313 0 : ret = TALER_MHD_reply_with_error (connection,
314 : MHD_HTTP_BAD_REQUEST,
315 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
316 : "image");
317 0 : goto cleanup;
318 : }
319 :
320 22 : qs = TMH_db->insert_product (TMH_db->cls,
321 22 : mi->settings.id,
322 : product_id,
323 : &pd,
324 : num_cats,
325 : cats,
326 : &no_instance,
327 : &conflict,
328 : &no_cat,
329 : &no_group,
330 : &no_pot);
331 22 : switch (qs)
332 : {
333 0 : case GNUNET_DB_STATUS_HARD_ERROR:
334 : case GNUNET_DB_STATUS_SOFT_ERROR:
335 0 : ret = TALER_MHD_reply_with_error (
336 : connection,
337 : MHD_HTTP_INTERNAL_SERVER_ERROR,
338 : (GNUNET_DB_STATUS_SOFT_ERROR == qs)
339 : ? TALER_EC_GENERIC_DB_SOFT_FAILURE
340 : : TALER_EC_GENERIC_DB_COMMIT_FAILED,
341 : NULL);
342 0 : goto cleanup;
343 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
344 0 : ret = TALER_MHD_reply_with_error (
345 : connection,
346 : MHD_HTTP_INTERNAL_SERVER_ERROR,
347 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
348 : NULL);
349 0 : goto cleanup;
350 22 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
351 22 : break;
352 : }
353 22 : if (no_instance)
354 : {
355 0 : ret = TALER_MHD_reply_with_error (
356 : connection,
357 : MHD_HTTP_NOT_FOUND,
358 : TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
359 0 : mi->settings.id);
360 0 : goto cleanup;
361 : }
362 22 : if (no_group)
363 : {
364 0 : ret = TALER_MHD_reply_with_error (
365 : connection,
366 : MHD_HTTP_NOT_FOUND,
367 : TALER_EC_MERCHANT_GENERIC_PRODUCT_GROUP_UNKNOWN,
368 : NULL);
369 0 : goto cleanup;
370 : }
371 22 : if (no_pot)
372 : {
373 0 : ret = TALER_MHD_reply_with_error (
374 : connection,
375 : MHD_HTTP_NOT_FOUND,
376 : TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
377 : NULL);
378 0 : goto cleanup;
379 : }
380 22 : if (conflict)
381 : {
382 2 : ret = TALER_MHD_reply_with_error (
383 : connection,
384 : MHD_HTTP_CONFLICT,
385 : TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
386 : product_id);
387 2 : goto cleanup;
388 : }
389 20 : if (-1 != no_cat)
390 : {
391 : char nocats[24];
392 :
393 0 : GNUNET_break_op (0);
394 0 : TMH_db->rollback (TMH_db->cls);
395 0 : GNUNET_snprintf (nocats,
396 : sizeof (nocats),
397 : "%llu",
398 : (unsigned long long) no_cat);
399 0 : ret = TALER_MHD_reply_with_error (
400 : connection,
401 : MHD_HTTP_NOT_FOUND,
402 : TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
403 : nocats);
404 0 : goto cleanup;
405 : }
406 20 : ret = TALER_MHD_reply_static (connection,
407 : MHD_HTTP_NO_CONTENT,
408 : NULL,
409 : NULL,
410 : 0);
411 22 : cleanup:
412 22 : GNUNET_JSON_parse_free (spec);
413 22 : GNUNET_free (pd.price_array);
414 22 : GNUNET_free (cats);
415 22 : return ret;
416 : }
417 :
418 :
419 : /* end of taler-merchant-httpd_private-post-products.c */
|