Line data Source code
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 32 : handle_get_product_finished (void *cls,
87 : long response_code,
88 : const void *response)
89 : {
90 32 : struct TALER_MERCHANT_GetPrivateProductHandle *gpp = cls;
91 32 : const json_t *json = response;
92 32 : struct TALER_MERCHANT_GetPrivateProductResponse pgr = {
93 32 : .hr.http_status = (unsigned int) response_code,
94 : .hr.reply = json
95 : };
96 :
97 32 : gpp->job = NULL;
98 32 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
99 : "Got /private/products/$PRODUCT_ID response with status code %u\n",
100 : (unsigned int) response_code);
101 32 : switch (response_code)
102 : {
103 18 : case MHD_HTTP_OK:
104 : {
105 18 : const json_t *jcategories = NULL;
106 : struct GNUNET_JSON_Specification spec[] = {
107 18 : GNUNET_JSON_spec_string (
108 : "product_name",
109 : &pgr.details.ok.product_name),
110 18 : GNUNET_JSON_spec_string (
111 : "description",
112 : &pgr.details.ok.description),
113 18 : GNUNET_JSON_spec_object_const (
114 : "description_i18n",
115 : &pgr.details.ok.description_i18n),
116 18 : TALER_JSON_spec_slug (
117 : "unit",
118 : &pgr.details.ok.unit),
119 18 : 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 18 : TALER_JSON_spec_amount_any (
124 : "price",
125 : &pgr.details.ok.price),
126 18 : GNUNET_JSON_spec_mark_optional (
127 : GNUNET_JSON_spec_string (
128 : "image",
129 : &pgr.details.ok.image),
130 : NULL),
131 18 : GNUNET_JSON_spec_mark_optional (
132 : GNUNET_JSON_spec_array_const (
133 : "taxes",
134 : &pgr.details.ok.taxes),
135 : NULL),
136 18 : GNUNET_JSON_spec_int64 (
137 : "total_stock",
138 : &pgr.details.ok.total_stock),
139 18 : GNUNET_JSON_spec_string (
140 : "unit_total_stock",
141 : &pgr.details.ok.unit_total_stock),
142 18 : GNUNET_JSON_spec_bool (
143 : "unit_allow_fraction",
144 : &pgr.details.ok.unit_allow_fraction),
145 18 : GNUNET_JSON_spec_uint32 (
146 : "unit_precision_level",
147 : &pgr.details.ok.unit_precision_level),
148 18 : GNUNET_JSON_spec_uint64 (
149 : "total_sold",
150 : &pgr.details.ok.total_sold),
151 18 : GNUNET_JSON_spec_uint64 (
152 : "total_lost",
153 : &pgr.details.ok.total_lost),
154 18 : GNUNET_JSON_spec_mark_optional (
155 : GNUNET_JSON_spec_object_const (
156 : "address",
157 : &pgr.details.ok.location),
158 : NULL),
159 18 : GNUNET_JSON_spec_mark_optional (
160 : GNUNET_JSON_spec_timestamp (
161 : "next_restock",
162 : &pgr.details.ok.next_restock),
163 : NULL),
164 18 : GNUNET_JSON_spec_mark_optional (
165 : GNUNET_JSON_spec_array_const (
166 : "categories",
167 : &jcategories),
168 : NULL),
169 18 : GNUNET_JSON_spec_mark_optional (
170 : GNUNET_JSON_spec_uint64 (
171 : "product_group_id",
172 : &pgr.details.ok.product_group_id),
173 : NULL),
174 18 : GNUNET_JSON_spec_mark_optional (
175 : GNUNET_JSON_spec_uint64 (
176 : "money_pot_id",
177 : &pgr.details.ok.money_pot_id),
178 : NULL),
179 18 : GNUNET_JSON_spec_mark_optional (
180 : GNUNET_JSON_spec_bool (
181 : "price_is_net",
182 : &pgr.details.ok.price_is_net),
183 : NULL),
184 18 : GNUNET_JSON_spec_mark_optional (
185 : GNUNET_JSON_spec_uint16 (
186 : "minimum_age",
187 : &pgr.details.ok.minimum_age),
188 : NULL),
189 18 : GNUNET_JSON_spec_end ()
190 : };
191 :
192 18 : if (GNUNET_OK !=
193 18 : GNUNET_JSON_parse (json,
194 : spec,
195 : NULL, NULL))
196 : {
197 0 : pgr.hr.http_status = 0;
198 0 : pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
199 0 : break;
200 : }
201 : {
202 : unsigned int num_categories;
203 : uint64_t *categories;
204 :
205 18 : num_categories = (unsigned int) json_array_size (jcategories);
206 18 : if ( (json_array_size (jcategories) != (size_t) num_categories) ||
207 : (num_categories > MAX_CATEGORIES) )
208 : {
209 0 : GNUNET_break_op (0);
210 0 : pgr.hr.http_status = 0;
211 0 : pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
212 0 : break;
213 : }
214 18 : categories = GNUNET_new_array (num_categories,
215 : uint64_t);
216 24 : for (unsigned int i = 0; i < num_categories; i++)
217 : {
218 6 : json_t *jval = json_array_get (jcategories,
219 : i);
220 :
221 6 : if (! json_is_integer (jval))
222 : {
223 0 : GNUNET_break_op (0);
224 0 : GNUNET_free (categories);
225 0 : pgr.hr.http_status = 0;
226 0 : pgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
227 0 : goto done;
228 : }
229 6 : categories[i] = (uint64_t) json_integer_value (jval);
230 : }
231 18 : pgr.details.ok.categories_len = num_categories;
232 18 : pgr.details.ok.categories = categories;
233 18 : gpp->cb (gpp->cb_cls,
234 : &pgr);
235 18 : GNUNET_free (categories);
236 18 : TALER_MERCHANT_get_private_product_cancel (gpp);
237 18 : return;
238 : }
239 : }
240 4 : case MHD_HTTP_UNAUTHORIZED:
241 4 : pgr.hr.ec = TALER_JSON_get_error_code (json);
242 4 : pgr.hr.hint = TALER_JSON_get_error_hint (json);
243 4 : break;
244 10 : case MHD_HTTP_NOT_FOUND:
245 10 : pgr.hr.ec = TALER_JSON_get_error_code (json);
246 10 : pgr.hr.hint = TALER_JSON_get_error_hint (json);
247 10 : break;
248 0 : default:
249 0 : pgr.hr.ec = TALER_JSON_get_error_code (json);
250 0 : pgr.hr.hint = TALER_JSON_get_error_hint (json);
251 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
252 : "Unexpected response code %u/%d\n",
253 : (unsigned int) response_code,
254 : (int) pgr.hr.ec);
255 0 : break;
256 : }
257 14 : done:
258 14 : gpp->cb (gpp->cb_cls,
259 : &pgr);
260 14 : TALER_MERCHANT_get_private_product_cancel (gpp);
261 : }
262 :
263 :
264 : struct TALER_MERCHANT_GetPrivateProductHandle *
265 32 : 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 32 : gpp = GNUNET_new (struct TALER_MERCHANT_GetPrivateProductHandle);
273 32 : gpp->ctx = ctx;
274 32 : gpp->base_url = GNUNET_strdup (url);
275 32 : gpp->product_id = GNUNET_strdup (product_id);
276 32 : return gpp;
277 : }
278 :
279 :
280 : enum TALER_ErrorCode
281 32 : 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 32 : gpp->cb = cb;
289 32 : gpp->cb_cls = cb_cls;
290 : {
291 : char *path;
292 :
293 32 : GNUNET_asprintf (&path,
294 : "private/products/%s",
295 : gpp->product_id);
296 32 : gpp->url = TALER_url_join (gpp->base_url,
297 : path,
298 : NULL);
299 32 : GNUNET_free (path);
300 : }
301 32 : if (NULL == gpp->url)
302 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
303 32 : eh = TALER_MERCHANT_curl_easy_get_ (gpp->url);
304 32 : if (NULL == eh)
305 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
306 32 : gpp->job = GNUNET_CURL_job_add (gpp->ctx,
307 : eh,
308 : &handle_get_product_finished,
309 : gpp);
310 32 : if (NULL == gpp->job)
311 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
312 32 : return TALER_EC_NONE;
313 : }
314 :
315 :
316 : void
317 32 : TALER_MERCHANT_get_private_product_cancel (
318 : struct TALER_MERCHANT_GetPrivateProductHandle *gpp)
319 : {
320 32 : if (NULL != gpp->job)
321 : {
322 0 : GNUNET_CURL_job_cancel (gpp->job);
323 0 : gpp->job = NULL;
324 : }
325 32 : GNUNET_free (gpp->url);
326 32 : GNUNET_free (gpp->product_id);
327 32 : GNUNET_free (gpp->base_url);
328 32 : GNUNET_free (gpp);
329 32 : }
330 :
331 :
332 : /* end of merchant_api_get-private-products-PRODUCT_ID-new.c */
|