Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-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 merchant_api_get-orders-ORDER_ID-new.c
19 : * @brief Implementation of the GET /orders/$ORDER_ID request (wallet-facing)
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/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-orders-ORDER_ID.h>
29 : #include "merchant_api_curl_defaults.h"
30 : #include "merchant_api_common.h"
31 : #include <taler/taler_json_lib.h>
32 :
33 :
34 : /**
35 : * Handle for a GET /orders/$ORDER_ID operation (wallet-facing).
36 : */
37 : struct TALER_MERCHANT_GetOrdersHandle
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_GetOrdersCallback cb;
58 :
59 : /**
60 : * Closure for @a cb.
61 : */
62 : TALER_MERCHANT_GET_ORDERS_RESULT_CLOSURE *cb_cls;
63 :
64 : /**
65 : * Reference to the execution context.
66 : */
67 : struct GNUNET_CURL_Context *ctx;
68 :
69 : /**
70 : * Order ID.
71 : */
72 : char *order_id;
73 :
74 : /**
75 : * Hash of the contract terms (for authentication).
76 : */
77 : struct TALER_PrivateContractHashP h_contract;
78 :
79 : /**
80 : * Claim token for unclaimed order authentication.
81 : */
82 : struct TALER_ClaimTokenP token;
83 :
84 : /**
85 : * Session ID for repurchase detection, or NULL.
86 : */
87 : char *session_id;
88 :
89 : /**
90 : * Long polling timeout.
91 : */
92 : struct GNUNET_TIME_Relative timeout;
93 :
94 : /**
95 : * Minimum refund amount to wait for, or NULL if unset.
96 : */
97 : struct TALER_Amount min_refund;
98 :
99 : /**
100 : * Whether refunded orders count for repurchase detection.
101 : */
102 : enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
103 :
104 : /**
105 : * True if @e h_contract was set.
106 : */
107 : bool have_h_contract;
108 :
109 : /**
110 : * True if @e token was set.
111 : */
112 : bool have_token;
113 :
114 : /**
115 : * True if @e min_refund was set.
116 : */
117 : bool have_min_refund;
118 :
119 : /**
120 : * If true, wait until refund is confirmed obtained.
121 : */
122 : bool await_refund_obtained;
123 : };
124 :
125 :
126 : /**
127 : * Function called when we're done processing the
128 : * HTTP GET /orders/$ORDER_ID request (wallet-facing).
129 : *
130 : * @param cls the `struct TALER_MERCHANT_GetOrdersHandle`
131 : * @param response_code HTTP response code, 0 on error
132 : * @param response response body, NULL if not in JSON
133 : */
134 : static void
135 0 : handle_get_order_finished (void *cls,
136 : long response_code,
137 : const void *response)
138 : {
139 0 : struct TALER_MERCHANT_GetOrdersHandle *oph = cls;
140 0 : const json_t *json = response;
141 0 : struct TALER_MERCHANT_GetOrdersResponse owgr = {
142 0 : .hr.http_status = (unsigned int) response_code,
143 : .hr.reply = json
144 : };
145 :
146 0 : oph->job = NULL;
147 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
148 : "Got /orders/$ORDER_ID response with status code %u\n",
149 : (unsigned int) response_code);
150 0 : switch (response_code)
151 : {
152 0 : case MHD_HTTP_OK:
153 : {
154 : struct GNUNET_JSON_Specification spec[] = {
155 0 : GNUNET_JSON_spec_bool ("refunded",
156 : &owgr.details.ok.refunded),
157 0 : GNUNET_JSON_spec_bool ("refund_pending",
158 : &owgr.details.ok.refund_pending),
159 0 : TALER_JSON_spec_amount_any ("refund_amount",
160 : &owgr.details.ok.refund_amount),
161 0 : GNUNET_JSON_spec_mark_optional (
162 : TALER_JSON_spec_amount_any ("refund_taken",
163 : &owgr.details.ok.refund_taken),
164 : NULL),
165 0 : GNUNET_JSON_spec_end ()
166 : };
167 :
168 0 : if (GNUNET_OK !=
169 0 : GNUNET_JSON_parse (json,
170 : spec,
171 : NULL, NULL))
172 : {
173 0 : owgr.hr.http_status = 0;
174 0 : owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
175 0 : break;
176 : }
177 0 : oph->cb (oph->cb_cls,
178 : &owgr);
179 0 : TALER_MERCHANT_get_orders_cancel (oph);
180 0 : return;
181 : }
182 0 : case MHD_HTTP_ACCEPTED:
183 : {
184 : struct GNUNET_JSON_Specification spec[] = {
185 0 : GNUNET_JSON_spec_string (
186 : "public_reorder_url",
187 : &owgr.details.accepted.public_reorder_url),
188 0 : GNUNET_JSON_spec_end ()
189 : };
190 :
191 0 : if (GNUNET_OK !=
192 0 : GNUNET_JSON_parse (json,
193 : spec,
194 : NULL, NULL))
195 : {
196 0 : owgr.hr.http_status = 0;
197 0 : owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
198 0 : break;
199 : }
200 0 : oph->cb (oph->cb_cls,
201 : &owgr);
202 0 : TALER_MERCHANT_get_orders_cancel (oph);
203 0 : return;
204 : }
205 0 : case MHD_HTTP_FOUND:
206 : /* Redirect; Location header has the target URL.
207 : No JSON body expected. */
208 0 : break;
209 0 : case MHD_HTTP_PAYMENT_REQUIRED:
210 : {
211 : struct GNUNET_JSON_Specification spec[] = {
212 0 : GNUNET_JSON_spec_string (
213 : "taler_pay_uri",
214 : &owgr.details.payment_required.taler_pay_uri),
215 0 : GNUNET_JSON_spec_mark_optional (
216 : GNUNET_JSON_spec_string (
217 : "already_paid_order_id",
218 : &owgr.details.payment_required.already_paid_order_id),
219 : NULL),
220 0 : GNUNET_JSON_spec_mark_optional (
221 : GNUNET_JSON_spec_string (
222 : "fulfillment_url",
223 : &owgr.details.payment_required.fulfillment_url),
224 : NULL),
225 0 : GNUNET_JSON_spec_end ()
226 : };
227 :
228 0 : if (GNUNET_OK !=
229 0 : GNUNET_JSON_parse (json,
230 : spec,
231 : NULL, NULL))
232 : {
233 0 : owgr.hr.http_status = 0;
234 0 : owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
235 0 : break;
236 : }
237 0 : oph->cb (oph->cb_cls,
238 : &owgr);
239 0 : TALER_MERCHANT_get_orders_cancel (oph);
240 0 : return;
241 : }
242 0 : case MHD_HTTP_BAD_REQUEST:
243 0 : owgr.hr.ec = TALER_JSON_get_error_code (json);
244 0 : owgr.hr.hint = TALER_JSON_get_error_hint (json);
245 0 : break;
246 0 : case MHD_HTTP_FORBIDDEN:
247 0 : owgr.hr.ec = TALER_JSON_get_error_code (json);
248 0 : owgr.hr.hint = TALER_JSON_get_error_hint (json);
249 0 : break;
250 0 : case MHD_HTTP_NOT_FOUND:
251 0 : owgr.hr.ec = TALER_JSON_get_error_code (json);
252 0 : owgr.hr.hint = TALER_JSON_get_error_hint (json);
253 0 : break;
254 0 : case MHD_HTTP_NOT_ACCEPTABLE:
255 0 : owgr.hr.ec = TALER_JSON_get_error_code (json);
256 0 : owgr.hr.hint = TALER_JSON_get_error_hint (json);
257 0 : break;
258 0 : case MHD_HTTP_CONFLICT:
259 0 : owgr.hr.ec = TALER_JSON_get_error_code (json);
260 0 : owgr.hr.hint = TALER_JSON_get_error_hint (json);
261 0 : break;
262 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
263 0 : owgr.hr.ec = TALER_JSON_get_error_code (json);
264 0 : owgr.hr.hint = TALER_JSON_get_error_hint (json);
265 0 : break;
266 0 : default:
267 0 : TALER_MERCHANT_parse_error_details_ (json,
268 : response_code,
269 : &owgr.hr);
270 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
271 : "Unexpected response code %u/%d\n",
272 : (unsigned int) response_code,
273 : (int) owgr.hr.ec);
274 0 : break;
275 : }
276 0 : oph->cb (oph->cb_cls,
277 : &owgr);
278 0 : TALER_MERCHANT_get_orders_cancel (oph);
279 : }
280 :
281 :
282 : struct TALER_MERCHANT_GetOrdersHandle *
283 0 : TALER_MERCHANT_get_orders_create (
284 : struct GNUNET_CURL_Context *ctx,
285 : const char *url,
286 : const char *order_id)
287 : {
288 : struct TALER_MERCHANT_GetOrdersHandle *oph;
289 :
290 0 : oph = GNUNET_new (struct TALER_MERCHANT_GetOrdersHandle);
291 0 : oph->ctx = ctx;
292 0 : oph->base_url = GNUNET_strdup (url);
293 0 : oph->order_id = GNUNET_strdup (order_id);
294 0 : oph->allow_refunded_for_repurchase = TALER_EXCHANGE_YNA_NO;
295 0 : return oph;
296 : }
297 :
298 :
299 : enum GNUNET_GenericReturnValue
300 0 : TALER_MERCHANT_get_orders_set_options_ (
301 : struct TALER_MERCHANT_GetOrdersHandle *oph,
302 : unsigned int num_options,
303 : const struct TALER_MERCHANT_GetOrdersOptionValue *options)
304 : {
305 0 : for (unsigned int i = 0; i < num_options; i++)
306 : {
307 0 : const struct TALER_MERCHANT_GetOrdersOptionValue *opt =
308 0 : &options[i];
309 :
310 0 : switch (opt->option)
311 : {
312 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_END:
313 0 : return GNUNET_OK;
314 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_TIMEOUT:
315 0 : oph->timeout = opt->details.timeout;
316 0 : break;
317 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_SESSION_ID:
318 0 : GNUNET_free (oph->session_id);
319 0 : if (NULL != opt->details.session_id)
320 0 : oph->session_id = GNUNET_strdup (opt->details.session_id);
321 0 : break;
322 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_MIN_REFUND:
323 0 : oph->min_refund = opt->details.min_refund;
324 0 : oph->have_min_refund = true;
325 0 : break;
326 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_AWAIT_REFUND_OBTAINED:
327 0 : oph->await_refund_obtained = opt->details.await_refund_obtained;
328 0 : break;
329 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_H_CONTRACT:
330 0 : oph->h_contract = opt->details.h_contract;
331 0 : oph->have_h_contract = true;
332 0 : break;
333 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_TOKEN:
334 0 : oph->token = opt->details.token;
335 0 : oph->have_token = true;
336 0 : break;
337 0 : case TALER_MERCHANT_GET_ORDERS_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE:
338 : oph->allow_refunded_for_repurchase
339 0 : = opt->details.allow_refunded_for_repurchase;
340 0 : break;
341 0 : default:
342 0 : GNUNET_break (0);
343 0 : return GNUNET_NO;
344 : }
345 : }
346 0 : return GNUNET_OK;
347 : }
348 :
349 :
350 : enum TALER_ErrorCode
351 0 : TALER_MERCHANT_get_orders_start (
352 : struct TALER_MERCHANT_GetOrdersHandle *oph,
353 : TALER_MERCHANT_GetOrdersCallback cb,
354 : TALER_MERCHANT_GET_ORDERS_RESULT_CLOSURE *cb_cls)
355 : {
356 : CURL *eh;
357 : unsigned int tms;
358 :
359 0 : oph->cb = cb;
360 0 : oph->cb_cls = cb_cls;
361 0 : tms = (unsigned int) (oph->timeout.rel_value_us
362 0 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
363 : {
364 : struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s;
365 0 : char *token_s = NULL;
366 : char *path;
367 : char timeout_ms[32];
368 : const char *yna_s;
369 :
370 0 : if (oph->have_h_contract)
371 0 : GNUNET_CRYPTO_hash_to_enc (&oph->h_contract.hash,
372 : &h_contract_s);
373 0 : if (oph->have_token)
374 0 : token_s = GNUNET_STRINGS_data_to_string_alloc (
375 0 : &oph->token,
376 : sizeof (oph->token));
377 0 : GNUNET_snprintf (timeout_ms,
378 : sizeof (timeout_ms),
379 : "%u",
380 : tms);
381 0 : GNUNET_asprintf (&path,
382 : "orders/%s",
383 : oph->order_id);
384 0 : yna_s = (TALER_EXCHANGE_YNA_NO != oph->allow_refunded_for_repurchase)
385 0 : ? TALER_yna_to_string (oph->allow_refunded_for_repurchase)
386 0 : : NULL;
387 0 : oph->url = TALER_url_join (oph->base_url,
388 : path,
389 : "h_contract",
390 0 : oph->have_h_contract
391 : ? h_contract_s.encoding
392 : : NULL,
393 : "token",
394 : token_s,
395 : "session_id",
396 : oph->session_id,
397 : "timeout_ms",
398 : (0 != tms)
399 : ? timeout_ms
400 : : NULL,
401 : "refund",
402 0 : oph->have_min_refund
403 0 : ? TALER_amount2s (&oph->min_refund)
404 : : NULL,
405 : "await_refund_obtained",
406 0 : oph->await_refund_obtained
407 : ? "yes"
408 : : NULL,
409 : "allow_refunded_for_repurchase",
410 : yna_s,
411 : NULL);
412 0 : GNUNET_free (path);
413 0 : GNUNET_free (token_s);
414 : }
415 0 : if (NULL == oph->url)
416 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
417 0 : eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
418 0 : if (NULL == eh)
419 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
420 0 : if (0 != tms)
421 : {
422 0 : GNUNET_break (CURLE_OK ==
423 : curl_easy_setopt (eh,
424 : CURLOPT_TIMEOUT_MS,
425 : (long) (tms + 100L)));
426 : }
427 0 : oph->job = GNUNET_CURL_job_add (oph->ctx,
428 : eh,
429 : &handle_get_order_finished,
430 : oph);
431 0 : if (NULL == oph->job)
432 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
433 0 : return TALER_EC_NONE;
434 : }
435 :
436 :
437 : void
438 0 : TALER_MERCHANT_get_orders_cancel (
439 : struct TALER_MERCHANT_GetOrdersHandle *oph)
440 : {
441 0 : if (NULL != oph->job)
442 : {
443 0 : GNUNET_CURL_job_cancel (oph->job);
444 0 : oph->job = NULL;
445 : }
446 0 : GNUNET_free (oph->url);
447 0 : GNUNET_free (oph->order_id);
448 0 : GNUNET_free (oph->session_id);
449 0 : GNUNET_free (oph->base_url);
450 0 : GNUNET_free (oph);
451 0 : }
452 :
453 :
454 : /* end of merchant_api_get-orders-ORDER_ID-new.c */
|