Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free Software
7 : Foundation; either version 3, 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 General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-merchant-httpd_get-orders-ID.c
18 : * @brief implementation of GET /orders/$ID
19 : * @author Marcello Stanisci
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <gnunet/gnunet_uri_lib.h>
25 : #include <taler/taler_signatures.h>
26 : #include <taler/taler_dbevents.h>
27 : #include <taler/taler_json_lib.h>
28 : #include <taler/taler_exchange_service.h>
29 : #include "taler-merchant-httpd_exchanges.h"
30 : #include "taler-merchant-httpd_get-orders-ID.h"
31 : #include "taler-merchant-httpd_mhd.h"
32 : #include "taler-merchant-httpd_qr.h"
33 : #include "taler-merchant-httpd_templating.h"
34 :
35 : /**
36 : * How often do we retry DB transactions on serialization failures?
37 : */
38 : #define MAX_RETRIES 5
39 :
40 :
41 : /**
42 : * Context for the operation.
43 : */
44 : struct GetOrderData
45 : {
46 :
47 : /**
48 : * Hashed version of contract terms. All zeros if not provided.
49 : */
50 : struct TALER_PrivateContractHashP h_contract_terms;
51 :
52 : /**
53 : * Claim token used for access control. All zeros if not provided.
54 : */
55 : struct TALER_ClaimTokenP claim_token;
56 :
57 : /**
58 : * DLL of (suspended) requests.
59 : */
60 : struct GetOrderData *next;
61 :
62 : /**
63 : * DLL of (suspended) requests.
64 : */
65 : struct GetOrderData *prev;
66 :
67 : /**
68 : * Context of the request.
69 : */
70 : struct TMH_HandlerContext *hc;
71 :
72 : /**
73 : * Entry in the #resume_timeout_heap for this check payment, if we are
74 : * suspended.
75 : */
76 : struct TMH_SuspendedConnection sc;
77 :
78 : /**
79 : * Database event we are waiting on to be resuming.
80 : */
81 : struct GNUNET_DB_EventHandler *pay_eh;
82 :
83 : /**
84 : * Database event we are waiting on to be resuming.
85 : */
86 : struct GNUNET_DB_EventHandler *refund_eh;
87 :
88 : /**
89 : * Which merchant instance is this for?
90 : */
91 : struct MerchantInstance *mi;
92 :
93 : /**
94 : * order ID for the payment
95 : */
96 : const char *order_id;
97 :
98 : /**
99 : * Where to get the contract
100 : */
101 : const char *contract_url;
102 :
103 : /**
104 : * fulfillment URL of the contract (valid as long as @e contract_terms is
105 : * valid; but can also be NULL if the contract_terms does not come with
106 : * a fulfillment URL).
107 : */
108 : const char *fulfillment_url;
109 :
110 : /**
111 : * session of the client
112 : */
113 : const char *session_id;
114 :
115 : /**
116 : * Contract terms of the payment we are checking. NULL when they
117 : * are not (yet) known.
118 : */
119 : json_t *contract_terms;
120 :
121 : /**
122 : * Total refunds granted for this payment. Only initialized
123 : * if @e refunded is set to true.
124 : */
125 : struct TALER_Amount refund_amount;
126 :
127 : /**
128 : * Total refunds already collected.
129 : * if @e refunded is set to true.
130 : */
131 : struct TALER_Amount refund_taken;
132 :
133 : /**
134 : * Return code: #TALER_EC_NONE if successful.
135 : */
136 : enum TALER_ErrorCode ec;
137 :
138 : /**
139 : * Did we suspend @a connection and are thus in
140 : * the #god_head DLL (#GNUNET_YES). Set to
141 : * #GNUNET_NO if we are not suspended, and to
142 : * #GNUNET_SYSERR if we should close the connection
143 : * without a response due to shutdown.
144 : */
145 : enum GNUNET_GenericReturnValue suspended;
146 :
147 : /**
148 : * Set to true if we are dealing with a claimed order
149 : * (and thus @e h_contract_terms is set, otherwise certain
150 : * DB queries will not work).
151 : */
152 : bool claimed;
153 :
154 : /**
155 : * Set to true if this payment has been refunded and
156 : * @e refund_amount is initialized.
157 : */
158 : bool refunded;
159 :
160 : /**
161 : * Set to true if a refund is still available for the
162 : * wallet for this payment.
163 : * @deprecated: true if refund_taken < refund_amount
164 : */
165 : bool refund_pending;
166 :
167 : /**
168 : * Set to true if the client requested HTML, otherwise we generate JSON.
169 : */
170 : bool generate_html;
171 :
172 : };
173 :
174 :
175 : /**
176 : * Head of DLL of (suspended) requests.
177 : */
178 : static struct GetOrderData *god_head;
179 :
180 : /**
181 : * Tail of DLL of (suspended) requests.
182 : */
183 : static struct GetOrderData *god_tail;
184 :
185 :
186 : void
187 3 : TMH_force_wallet_get_order_resume (void)
188 : {
189 : struct GetOrderData *god;
190 :
191 3 : while (NULL != (god = god_head))
192 : {
193 0 : GNUNET_CONTAINER_DLL_remove (god_head,
194 : god_tail,
195 : god);
196 0 : GNUNET_assert (god->suspended);
197 0 : god->suspended = GNUNET_SYSERR;
198 0 : MHD_resume_connection (god->sc.con);
199 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
200 : }
201 3 : }
202 :
203 :
204 : /**
205 : * We have received a trigger from the database
206 : * that we should (possibly) resume the request.
207 : *
208 : * @param cls a `struct GetOrderData` to resume
209 : * @param extra string encoding refund amount (or NULL)
210 : * @param extra_size number of bytes in @a extra
211 : */
212 : static void
213 0 : resume_by_event (void *cls,
214 : const void *extra,
215 : size_t extra_size)
216 : {
217 0 : struct GetOrderData *god = cls;
218 : struct GNUNET_AsyncScopeSave old;
219 :
220 0 : GNUNET_async_scope_enter (&god->hc->async_scope_id,
221 : &old);
222 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
223 : "Received event for %s with argument `%.*s`\n",
224 : god->order_id,
225 : (int) extra_size,
226 : (const char *) extra);
227 0 : if (! god->suspended)
228 : {
229 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
230 : "Not suspended, ignoring event\n");
231 0 : GNUNET_async_scope_restore (&old);
232 0 : return; /* duplicate event is possible */
233 : }
234 0 : if (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) &&
235 0 : god->sc.awaiting_refund)
236 : {
237 : char *as;
238 : struct TALER_Amount a;
239 :
240 0 : if (0 == extra_size)
241 : {
242 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
243 : "No amount given, but need refund above threshold\n");
244 0 : GNUNET_async_scope_restore (&old);
245 0 : return; /* not relevant */
246 : }
247 0 : as = GNUNET_strndup (extra,
248 : extra_size);
249 0 : if (GNUNET_OK !=
250 0 : TALER_string_to_amount (as,
251 : &a))
252 : {
253 0 : GNUNET_break (0);
254 0 : GNUNET_async_scope_restore (&old);
255 0 : return;
256 : }
257 0 : if (GNUNET_OK !=
258 0 : TALER_amount_cmp_currency (&god->sc.refund_expected,
259 : &a))
260 : {
261 0 : GNUNET_break (0);
262 0 : GNUNET_async_scope_restore (&old);
263 0 : return; /* bad currency!? */
264 : }
265 0 : if (1 == TALER_amount_cmp (&god->sc.refund_expected,
266 : &a))
267 : {
268 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
269 : "Amount too small to trigger resuming\n");
270 0 : GNUNET_async_scope_restore (&old);
271 0 : return; /* refund too small */
272 : }
273 : }
274 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
275 : "Resuming (%d/%d) by event with argument `%.*s`\n",
276 : (int) GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout),
277 : god->sc.awaiting_refund,
278 : (int) extra_size,
279 : (const char *) extra);
280 0 : god->suspended = GNUNET_NO;
281 0 : GNUNET_CONTAINER_DLL_remove (god_head,
282 : god_tail,
283 : god);
284 0 : MHD_resume_connection (god->sc.con);
285 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
286 0 : GNUNET_async_scope_restore (&old);
287 : }
288 :
289 :
290 : /**
291 : * Suspend this @a god until the trigger is satisfied.
292 : *
293 : * @param god request to suspend
294 : */
295 : static void
296 0 : suspend_god (struct GetOrderData *god)
297 : {
298 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
299 : "Suspending GET /orders/%s\n",
300 : god->order_id);
301 0 : if (NULL != god->contract_terms)
302 : {
303 0 : json_decref (god->contract_terms);
304 0 : god->fulfillment_url = NULL;
305 0 : god->contract_terms = NULL;
306 : }
307 0 : GNUNET_assert (! god->suspended);
308 0 : god->suspended = GNUNET_YES;
309 0 : GNUNET_CONTAINER_DLL_insert (god_head,
310 : god_tail,
311 : god);
312 0 : MHD_suspend_connection (god->sc.con);
313 0 : }
314 :
315 :
316 : /**
317 : * Create a taler://refund/ URI for the given @a con and @a order_id
318 : * and @a instance_id.
319 : *
320 : * @param merchant_base_url URL to take host and path from;
321 : * we cannot take it from the MHD connection as a browser
322 : * may have changed 'http' to 'https' and we MUST be consistent
323 : * with what the merchant's frontend used initially
324 : * @param order_id the order id
325 : * @return corresponding taler://refund/ URI, or NULL on missing "host"
326 : */
327 : static char *
328 0 : make_taler_refund_uri (const char *merchant_base_url,
329 : const char *order_id)
330 : {
331 0 : struct GNUNET_Buffer buf = { 0 };
332 : char *url;
333 : struct GNUNET_Uri uri;
334 :
335 0 : url = GNUNET_strdup (merchant_base_url);
336 0 : if (-1 == GNUNET_uri_parse (&uri,
337 : url))
338 : {
339 0 : GNUNET_break (0);
340 0 : GNUNET_free (url);
341 0 : return NULL;
342 : }
343 0 : GNUNET_assert (NULL != order_id);
344 0 : GNUNET_buffer_write_str (&buf,
345 : "taler");
346 0 : if (0 == strcasecmp ("http",
347 0 : uri.scheme))
348 0 : GNUNET_buffer_write_str (&buf,
349 : "+http");
350 0 : GNUNET_buffer_write_str (&buf,
351 : "://refund/");
352 0 : GNUNET_buffer_write_str (&buf,
353 0 : uri.host);
354 0 : if (0 != uri.port)
355 0 : GNUNET_buffer_write_fstr (&buf,
356 : ":%u",
357 0 : (unsigned int) uri.port);
358 0 : if (NULL != uri.path)
359 0 : GNUNET_buffer_write_path (&buf,
360 0 : uri.path);
361 0 : GNUNET_buffer_write_path (&buf,
362 : order_id);
363 0 : GNUNET_buffer_write_path (&buf,
364 : ""); // Trailing slash
365 0 : GNUNET_free (url);
366 0 : return GNUNET_buffer_reap_str (&buf);
367 : }
368 :
369 :
370 : char *
371 0 : TMH_make_order_status_url (struct MHD_Connection *con,
372 : const char *order_id,
373 : const char *session_id,
374 : const char *instance_id,
375 : struct TALER_ClaimTokenP *claim_token,
376 : struct TALER_PrivateContractHashP *h_contract)
377 : {
378 : const char *host;
379 : const char *forwarded_host;
380 : const char *uri_path;
381 0 : struct GNUNET_Buffer buf = { 0 };
382 : /* Number of query parameters written so far */
383 0 : unsigned int num_qp = 0;
384 :
385 0 : host = MHD_lookup_connection_value (con,
386 : MHD_HEADER_KIND,
387 : MHD_HTTP_HEADER_HOST);
388 0 : forwarded_host = MHD_lookup_connection_value (con,
389 : MHD_HEADER_KIND,
390 : "X-Forwarded-Host");
391 0 : uri_path = MHD_lookup_connection_value (con,
392 : MHD_HEADER_KIND,
393 : "X-Forwarded-Prefix");
394 0 : if (NULL != forwarded_host)
395 0 : host = forwarded_host;
396 0 : if (NULL == host)
397 : {
398 0 : GNUNET_break (0);
399 0 : return NULL;
400 : }
401 0 : if (NULL != strchr (host, '/'))
402 : {
403 0 : GNUNET_break_op (0);
404 0 : return NULL;
405 : }
406 0 : GNUNET_assert (NULL != instance_id);
407 0 : GNUNET_assert (NULL != order_id);
408 :
409 0 : if (GNUNET_NO == TALER_mhd_is_https (con))
410 0 : GNUNET_buffer_write_str (&buf,
411 : "http://");
412 : else
413 0 : GNUNET_buffer_write_str (&buf,
414 : "https://");
415 0 : GNUNET_buffer_write_str (&buf,
416 : host);
417 0 : if (NULL != uri_path)
418 0 : GNUNET_buffer_write_path (&buf,
419 : uri_path);
420 0 : if (0 != strcmp ("default",
421 : instance_id))
422 : {
423 0 : GNUNET_buffer_write_path (&buf,
424 : "instances");
425 0 : GNUNET_buffer_write_path (&buf,
426 : instance_id);
427 : }
428 0 : GNUNET_buffer_write_path (&buf,
429 : "/orders");
430 0 : GNUNET_buffer_write_path (&buf,
431 : order_id);
432 0 : if ((NULL != claim_token) &&
433 0 : (GNUNET_NO == GNUNET_is_zero (claim_token)))
434 : {
435 : /* 'token=' for human readability */
436 0 : GNUNET_buffer_write_str (&buf,
437 : "?token=");
438 0 : GNUNET_buffer_write_data_encoded (&buf,
439 : (char *) claim_token,
440 : sizeof (*claim_token));
441 0 : num_qp++;
442 : }
443 :
444 0 : if (NULL != session_id)
445 : {
446 0 : if (num_qp > 0)
447 0 : GNUNET_buffer_write_str (&buf,
448 : "&session_id=");
449 : else
450 0 : GNUNET_buffer_write_str (&buf,
451 : "?session_id=");
452 0 : GNUNET_buffer_write_str (&buf,
453 : session_id);
454 0 : num_qp++;
455 : }
456 :
457 0 : if (NULL != h_contract)
458 : {
459 0 : if (num_qp > 0)
460 0 : GNUNET_buffer_write_str (&buf,
461 : "&h_contract=");
462 : else
463 0 : GNUNET_buffer_write_str (&buf,
464 : "?h_contract=");
465 0 : GNUNET_buffer_write_data_encoded (&buf,
466 : (char *) h_contract,
467 : sizeof (*h_contract));
468 : }
469 :
470 0 : return GNUNET_buffer_reap_str (&buf);
471 : }
472 :
473 :
474 : char *
475 0 : TMH_make_taler_pay_uri (struct MHD_Connection *con,
476 : const char *order_id,
477 : const char *session_id,
478 : const char *instance_id,
479 : struct TALER_ClaimTokenP *claim_token)
480 : {
481 : const char *host;
482 : const char *forwarded_host;
483 : const char *uri_path;
484 0 : struct GNUNET_Buffer buf = { 0 };
485 :
486 0 : host = MHD_lookup_connection_value (con,
487 : MHD_HEADER_KIND,
488 : MHD_HTTP_HEADER_HOST);
489 0 : forwarded_host = MHD_lookup_connection_value (con,
490 : MHD_HEADER_KIND,
491 : "X-Forwarded-Host");
492 0 : uri_path = MHD_lookup_connection_value (con,
493 : MHD_HEADER_KIND,
494 : "X-Forwarded-Prefix");
495 0 : if (NULL != forwarded_host)
496 0 : host = forwarded_host;
497 0 : if (NULL == host)
498 : {
499 0 : GNUNET_break (0);
500 0 : return NULL;
501 : }
502 0 : if (NULL != strchr (host, '/'))
503 : {
504 0 : GNUNET_break_op (0);
505 0 : return NULL;
506 : }
507 0 : GNUNET_assert (NULL != instance_id);
508 0 : GNUNET_assert (NULL != order_id);
509 0 : GNUNET_buffer_write_str (&buf,
510 : "taler");
511 0 : if (GNUNET_NO == TALER_mhd_is_https (con))
512 0 : GNUNET_buffer_write_str (&buf,
513 : "+http");
514 0 : GNUNET_buffer_write_str (&buf,
515 : "://pay/");
516 0 : GNUNET_buffer_write_str (&buf,
517 : host);
518 0 : if (NULL != uri_path)
519 0 : GNUNET_buffer_write_path (&buf,
520 : uri_path);
521 0 : if (0 != strcmp ("default",
522 : instance_id))
523 : {
524 0 : GNUNET_buffer_write_path (&buf,
525 : "instances");
526 0 : GNUNET_buffer_write_path (&buf,
527 : instance_id);
528 : }
529 0 : GNUNET_buffer_write_path (&buf,
530 : order_id);
531 0 : GNUNET_buffer_write_path (&buf,
532 : (session_id == NULL) ? "" : session_id);
533 0 : if ((NULL != claim_token) &&
534 0 : (GNUNET_NO == GNUNET_is_zero (claim_token)))
535 : {
536 : /* Just 'c=' because this goes into QR
537 : codes, so this is more compact. */
538 0 : GNUNET_buffer_write_str (&buf,
539 : "?c=");
540 0 : GNUNET_buffer_write_data_encoded (&buf,
541 : (char *) claim_token,
542 : sizeof (struct TALER_ClaimTokenP));
543 : }
544 :
545 0 : return GNUNET_buffer_reap_str (&buf);
546 : }
547 :
548 :
549 : /**
550 : * Return the order summary of the contract of @a god in the
551 : * preferred language of the HTTP client.
552 : *
553 : * @param god order to extract summary from
554 : * @return dummy error message summary if no summary was provided in the contract
555 : */
556 : static const char *
557 0 : get_order_summary (const struct GetOrderData *god)
558 : {
559 : const char *language_pattern;
560 : const char *ret;
561 :
562 0 : language_pattern = MHD_lookup_connection_value (god->sc.con,
563 : MHD_HEADER_KIND,
564 : MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
565 0 : if (NULL == language_pattern)
566 0 : language_pattern = "en";
567 0 : ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms,
568 : language_pattern,
569 : "summary"));
570 0 : if (NULL == ret)
571 : {
572 : /* Upon order creation (and insertion into the database), the presence
573 : of a summary should have been checked. So if we get here, someone
574 : did something fishy to our database... */
575 0 : GNUNET_break (0);
576 0 : ret = "<bug: no summary>";
577 : }
578 0 : return ret;
579 : }
580 :
581 :
582 : /**
583 : * The client did not yet pay, send it the payment request.
584 : *
585 : * @param god check pay request context
586 : * @param already_paid_order_id if for the fulfillment URI there is
587 : * already a paid order, this is the order ID to redirect
588 : * the wallet to; NULL if not applicable
589 : * @return #MHD_YES on success
590 : */
591 : static MHD_RESULT
592 0 : send_pay_request (struct GetOrderData *god,
593 : const char *already_paid_order_id)
594 : {
595 : MHD_RESULT ret;
596 : char *taler_pay_uri;
597 : char *order_status_url;
598 : struct GNUNET_TIME_Relative remaining;
599 :
600 0 : remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
601 0 : if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
602 : (NULL == already_paid_order_id) )
603 : {
604 : /* long polling: do not queue a response, suspend connection instead */
605 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
606 : "Suspending request: long polling for payment\n");
607 0 : suspend_god (god);
608 0 : return MHD_YES;
609 : }
610 :
611 : /* Check if resource_id has been paid for in the same session
612 : * with another order_id.
613 : */
614 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
615 : "Sending payment request\n");
616 0 : taler_pay_uri = TMH_make_taler_pay_uri (god->sc.con,
617 : god->order_id,
618 : god->session_id,
619 0 : god->hc->instance->settings.id,
620 : &god->claim_token);
621 0 : order_status_url = TMH_make_order_status_url (god->sc.con,
622 : god->order_id,
623 : god->session_id,
624 0 : god->hc->instance->settings.id,
625 : &god->claim_token,
626 : NULL);
627 0 : if ( (NULL == taler_pay_uri) ||
628 : (NULL == order_status_url) )
629 : {
630 0 : GNUNET_break_op (0);
631 0 : GNUNET_free (taler_pay_uri);
632 0 : GNUNET_free (order_status_url);
633 0 : return TALER_MHD_reply_with_error (god->sc.con,
634 : MHD_HTTP_BAD_REQUEST,
635 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
636 : "host");
637 : }
638 0 : if (god->generate_html)
639 : {
640 0 : if (NULL != already_paid_order_id)
641 : {
642 : struct MHD_Response *reply;
643 :
644 0 : GNUNET_assert (NULL != god->fulfillment_url);
645 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
646 : "Redirecting to already paid order %s via fulfillment URL %s\n",
647 : already_paid_order_id,
648 : god->fulfillment_url);
649 0 : reply = MHD_create_response_from_buffer (0,
650 : NULL,
651 : MHD_RESPMEM_PERSISTENT);
652 0 : if (NULL == reply)
653 : {
654 0 : GNUNET_break (0);
655 0 : return MHD_NO;
656 : }
657 0 : GNUNET_break (MHD_YES ==
658 : MHD_add_response_header (reply,
659 : MHD_HTTP_HEADER_LOCATION,
660 : god->fulfillment_url));
661 : {
662 : MHD_RESULT ret;
663 :
664 0 : ret = MHD_queue_response (god->sc.con,
665 : MHD_HTTP_FOUND,
666 : reply);
667 0 : MHD_destroy_response (reply);
668 0 : return ret;
669 : }
670 : }
671 :
672 : {
673 : char *qr;
674 :
675 0 : qr = TMH_create_qrcode (taler_pay_uri);
676 0 : if (NULL == qr)
677 : {
678 0 : GNUNET_break (0);
679 0 : return MHD_NO;
680 : }
681 : {
682 : enum GNUNET_GenericReturnValue res;
683 : json_t *context;
684 :
685 0 : context = GNUNET_JSON_PACK (
686 : GNUNET_JSON_pack_string ("taler_pay_uri",
687 : taler_pay_uri),
688 : GNUNET_JSON_pack_string ("order_status_url",
689 : order_status_url),
690 : GNUNET_JSON_pack_string ("taler_pay_qrcode_svg",
691 : qr),
692 : GNUNET_JSON_pack_string ("order_summary",
693 : get_order_summary (god)));
694 0 : res = TMH_return_from_template (god->sc.con,
695 : MHD_HTTP_PAYMENT_REQUIRED,
696 : "request_payment",
697 0 : god->hc->instance->settings.id,
698 : taler_pay_uri,
699 : context);
700 0 : if (GNUNET_SYSERR == res)
701 : {
702 0 : GNUNET_break (0);
703 0 : ret = MHD_NO;
704 : }
705 : else
706 : {
707 0 : ret = MHD_YES;
708 : }
709 0 : json_decref (context);
710 : }
711 0 : GNUNET_free (qr);
712 : }
713 : }
714 : else /* end of 'generate HTML' */
715 : {
716 0 : ret = TALER_MHD_REPLY_JSON_PACK (
717 : god->sc.con,
718 : MHD_HTTP_PAYMENT_REQUIRED,
719 : GNUNET_JSON_pack_string ("taler_pay_uri",
720 : taler_pay_uri),
721 : GNUNET_JSON_pack_allow_null (
722 : GNUNET_JSON_pack_string ("fulfillment_url",
723 : god->fulfillment_url)),
724 : GNUNET_JSON_pack_allow_null (
725 : GNUNET_JSON_pack_string ("already_paid_order_id",
726 : already_paid_order_id)));
727 : }
728 0 : GNUNET_free (taler_pay_uri);
729 0 : GNUNET_free (order_status_url);
730 0 : return ret;
731 : }
732 :
733 :
734 : /**
735 : * Function called with detailed information about a refund.
736 : * It is responsible for packing up the data to return.
737 : *
738 : * @param cls closure
739 : * @param refund_serial unique serial number of the refund
740 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
741 : * @param coin_pub public coin from which the refund comes from
742 : * @param exchange_url URL of the exchange that issued @a coin_pub
743 : * @param rtransaction_id identificator of the refund
744 : * @param reason human-readable explanation of the refund
745 : * @param refund_amount refund amount which is being taken from @a coin_pub
746 : * @param pending true if the this refund was not yet processed by the wallet/exchange
747 : */
748 : static void
749 0 : process_refunds_cb (void *cls,
750 : uint64_t refund_serial,
751 : struct GNUNET_TIME_Timestamp timestamp,
752 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
753 : const char *exchange_url,
754 : uint64_t rtransaction_id,
755 : const char *reason,
756 : const struct TALER_Amount *refund_amount,
757 : bool pending)
758 : {
759 0 : struct GetOrderData *god = cls;
760 :
761 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
762 : "Found refund of %s for coin %s with reason `%s' in database\n",
763 : TALER_amount2s (refund_amount),
764 : TALER_B2S (coin_pub),
765 : reason);
766 0 : god->refund_pending |= pending;
767 0 : if (! pending)
768 : {
769 0 : GNUNET_assert (0 <=
770 : TALER_amount_add (&god->refund_taken,
771 : &god->refund_taken,
772 : refund_amount));
773 : }
774 0 : GNUNET_assert (0 <=
775 : TALER_amount_add (&god->refund_amount,
776 : &god->refund_amount,
777 : refund_amount));
778 0 : god->refunded = true;
779 0 : }
780 :
781 :
782 : /**
783 : * Clean up the session state for a GET /orders/$ID request.
784 : *
785 : * @param cls must be a `struct GetOrderData *`
786 : */
787 : static void
788 0 : god_cleanup (void *cls)
789 : {
790 0 : struct GetOrderData *god = cls;
791 :
792 0 : if (NULL != god->contract_terms)
793 : {
794 0 : json_decref (god->contract_terms);
795 0 : god->contract_terms = NULL;
796 : }
797 0 : if (NULL != god->refund_eh)
798 : {
799 0 : TMH_db->event_listen_cancel (god->refund_eh);
800 0 : god->refund_eh = NULL;
801 : }
802 0 : if (NULL != god->pay_eh)
803 : {
804 0 : TMH_db->event_listen_cancel (god->pay_eh);
805 0 : god->pay_eh = NULL;
806 : }
807 0 : GNUNET_free (god);
808 0 : }
809 :
810 :
811 : MHD_RESULT
812 0 : TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
813 : struct MHD_Connection *connection,
814 : struct TMH_HandlerContext *hc)
815 : {
816 0 : struct GetOrderData *god = hc->ctx;
817 0 : const char *order_id = hc->infix;
818 : enum GNUNET_DB_QueryStatus qs;
819 0 : bool contract_match = false;
820 0 : bool token_match = false;
821 0 : bool h_contract_provided = false;
822 0 : bool claim_token_provided = false;
823 0 : bool contract_available = false;
824 : const char *merchant_base_url;
825 :
826 0 : if (NULL == god)
827 : {
828 0 : god = GNUNET_new (struct GetOrderData);
829 0 : hc->ctx = god;
830 0 : hc->cc = &god_cleanup;
831 0 : god->sc.con = connection;
832 0 : god->hc = hc;
833 0 : god->order_id = order_id;
834 0 : god->generate_html = TMH_MHD_test_html_desired (connection);
835 :
836 :
837 : /* first-time initialization / sanity checks */
838 : {
839 : const char *cts;
840 :
841 0 : cts = MHD_lookup_connection_value (connection,
842 : MHD_GET_ARGUMENT_KIND,
843 : "h_contract");
844 0 : if ( (NULL != cts) &&
845 : (GNUNET_OK !=
846 0 : GNUNET_CRYPTO_hash_from_string (cts,
847 : &god->h_contract_terms.hash)) )
848 : {
849 : /* cts has wrong encoding */
850 0 : GNUNET_break_op (0);
851 0 : return TALER_MHD_reply_with_error (connection,
852 : MHD_HTTP_BAD_REQUEST,
853 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
854 : "h_contract");
855 : }
856 0 : if (NULL != cts)
857 0 : h_contract_provided = true;
858 : }
859 :
860 : {
861 : const char *ct;
862 :
863 0 : ct = MHD_lookup_connection_value (connection,
864 : MHD_GET_ARGUMENT_KIND,
865 : "token");
866 0 : if ( (NULL != ct) &&
867 : (GNUNET_OK !=
868 0 : GNUNET_STRINGS_string_to_data (ct,
869 : strlen (ct),
870 0 : &god->claim_token,
871 : sizeof (god->claim_token))) )
872 : {
873 : /* ct has wrong encoding */
874 0 : GNUNET_break_op (0);
875 0 : return TALER_MHD_reply_with_error (connection,
876 : MHD_HTTP_BAD_REQUEST,
877 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
878 : "token");
879 : }
880 0 : if (NULL != ct)
881 0 : claim_token_provided = true;
882 : }
883 0 : god->session_id = MHD_lookup_connection_value (connection,
884 : MHD_GET_ARGUMENT_KIND,
885 : "session_id");
886 :
887 : /* process await_refund_obtained argument */
888 : {
889 : const char *await_refund_obtained_s;
890 :
891 : await_refund_obtained_s =
892 0 : MHD_lookup_connection_value (connection,
893 : MHD_GET_ARGUMENT_KIND,
894 : "await_refund_obtained");
895 0 : god->sc.awaiting_refund_obtained =
896 : (NULL != await_refund_obtained_s)
897 0 : ? 0 == strcasecmp (await_refund_obtained_s,
898 : "yes")
899 0 : : false;
900 0 : if (god->sc.awaiting_refund_obtained)
901 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
902 : "Awaiting refund obtained\n");
903 : }
904 :
905 : {
906 : const char *min_refund;
907 :
908 0 : min_refund = MHD_lookup_connection_value (connection,
909 : MHD_GET_ARGUMENT_KIND,
910 : "refund");
911 0 : if (NULL != min_refund)
912 : {
913 0 : if ( (GNUNET_OK !=
914 0 : TALER_string_to_amount (min_refund,
915 0 : &god->sc.refund_expected)) ||
916 0 : (0 != strcasecmp (god->sc.refund_expected.currency,
917 : TMH_currency) ) )
918 : {
919 0 : GNUNET_break_op (0);
920 0 : return TALER_MHD_reply_with_error (connection,
921 : MHD_HTTP_BAD_REQUEST,
922 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
923 : "refund");
924 : }
925 0 : god->sc.awaiting_refund = true;
926 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
927 : "Awaiting minimum refund of %s\n",
928 : min_refund);
929 : }
930 : }
931 :
932 :
933 : /* process timeout_ms argument */
934 : {
935 : const char *long_poll_timeout_ms;
936 :
937 0 : long_poll_timeout_ms = MHD_lookup_connection_value (connection,
938 : MHD_GET_ARGUMENT_KIND,
939 : "timeout_ms");
940 0 : if (NULL != long_poll_timeout_ms)
941 : {
942 : unsigned int timeout_ms;
943 : char dummy;
944 :
945 0 : if (1 != sscanf (long_poll_timeout_ms,
946 : "%u%c",
947 : &timeout_ms,
948 : &dummy))
949 : {
950 0 : GNUNET_break_op (0);
951 0 : return TALER_MHD_reply_with_error (connection,
952 : MHD_HTTP_BAD_REQUEST,
953 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
954 : "timeout_ms (must be non-negative number)");
955 : }
956 : /* If HTML is requested, we never long poll. Makes no sense */
957 0 : if (! god->generate_html)
958 : {
959 : struct GNUNET_TIME_Relative timeout;
960 :
961 0 : timeout = GNUNET_TIME_relative_multiply (
962 : GNUNET_TIME_UNIT_MILLISECONDS,
963 : timeout_ms);
964 : god->sc.long_poll_timeout
965 0 : = GNUNET_TIME_relative_to_absolute (timeout);
966 0 : if (! GNUNET_TIME_relative_is_zero (timeout))
967 : {
968 0 : if (god->sc.awaiting_refund ||
969 0 : god->sc.awaiting_refund_obtained)
970 : {
971 0 : struct TMH_OrderPayEventP refund_eh = {
972 0 : .header.size = htons (sizeof (refund_eh)),
973 0 : .header.type = htons (god->sc.awaiting_refund_obtained
974 : ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
975 : : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
976 0 : .merchant_pub = hc->instance->merchant_pub
977 : };
978 :
979 0 : GNUNET_CRYPTO_hash (god->order_id,
980 : strlen (god->order_id),
981 : &refund_eh.h_order_id);
982 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
983 : "Subscribing %p to refunds on %s\n",
984 : god,
985 : god->order_id);
986 0 : god->refund_eh = TMH_db->event_listen (TMH_db->cls,
987 : &refund_eh.header,
988 : timeout,
989 : &resume_by_event,
990 : god);
991 : }
992 : {
993 0 : struct TMH_OrderPayEventP pay_eh = {
994 0 : .header.size = htons (sizeof (pay_eh)),
995 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
996 0 : .merchant_pub = hc->instance->merchant_pub
997 : };
998 :
999 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1000 : "Subscribing to payments on %s\n",
1001 : god->order_id);
1002 0 : GNUNET_CRYPTO_hash (god->order_id,
1003 : strlen (god->order_id),
1004 : &pay_eh.h_order_id);
1005 0 : god->pay_eh = TMH_db->event_listen (TMH_db->cls,
1006 : &pay_eh.header,
1007 : timeout,
1008 : &resume_by_event,
1009 : god);
1010 : }
1011 : } /* end of timeout non-zero */
1012 : } /* end of HTML generation NOT requested */
1013 : } /* end of timeout_ms argument provided */
1014 : } /* end of timeout_ms argument handling */
1015 :
1016 : } /* end of first-time initialization / sanity checks */
1017 :
1018 0 : if (GNUNET_SYSERR == god->suspended)
1019 0 : return MHD_NO; /* we are in shutdown */
1020 0 : if (GNUNET_YES == god->suspended)
1021 : {
1022 0 : god->suspended = GNUNET_NO;
1023 0 : GNUNET_CONTAINER_DLL_remove (god_head,
1024 : god_tail,
1025 : god);
1026 : }
1027 :
1028 : /* Convert order_id to h_contract_terms */
1029 0 : TMH_db->preflight (TMH_db->cls);
1030 0 : if (NULL == god->contract_terms)
1031 : {
1032 : uint64_t order_serial;
1033 0 : bool paid = false;
1034 : struct TALER_ClaimTokenP db_claim_token;
1035 :
1036 0 : qs = TMH_db->lookup_contract_terms (TMH_db->cls,
1037 0 : hc->instance->settings.id,
1038 : order_id,
1039 : &god->contract_terms,
1040 : &order_serial,
1041 : &paid,
1042 : &db_claim_token);
1043 0 : if (0 > qs)
1044 : {
1045 : /* single, read-only SQL statements should never cause
1046 : serialization problems */
1047 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
1048 : /* Always report on hard error as well to enable diagnostics */
1049 0 : GNUNET_break (0);
1050 0 : return TALER_MHD_reply_with_error (connection,
1051 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1052 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1053 : "lookup_contract_terms");
1054 : }
1055 :
1056 : /* Note: when "!ord.requireClaimToken" and the client does not provide
1057 : a claim token (all zeros!), then token_match==TRUE below: */
1058 0 : token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
1059 0 : && (0 == GNUNET_memcmp (&db_claim_token,
1060 : &god->claim_token));
1061 : }
1062 :
1063 : /* Check if client provided the right hash code of the contract terms */
1064 0 : if (NULL != god->contract_terms)
1065 : {
1066 0 : contract_available = true;
1067 :
1068 0 : if (GNUNET_YES == GNUNET_is_zero (&god->h_contract_terms))
1069 : {
1070 :
1071 0 : if (GNUNET_OK !=
1072 0 : TALER_JSON_contract_hash (god->contract_terms,
1073 : &god->h_contract_terms))
1074 : {
1075 0 : GNUNET_break (0);
1076 0 : return TALER_MHD_reply_with_error (connection,
1077 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1078 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1079 : "contract terms");
1080 : }
1081 :
1082 : }
1083 : else
1084 : {
1085 :
1086 : struct TALER_PrivateContractHashP h;
1087 :
1088 0 : if (GNUNET_OK !=
1089 0 : TALER_JSON_contract_hash (god->contract_terms,
1090 : &h))
1091 : {
1092 0 : GNUNET_break (0);
1093 0 : return TALER_MHD_reply_with_error (connection,
1094 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1095 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
1096 : "contract terms");
1097 : }
1098 0 : contract_match = (0 ==
1099 0 : GNUNET_memcmp (&h,
1100 : &god->h_contract_terms));
1101 0 : if (! contract_match)
1102 : {
1103 0 : GNUNET_break_op (0);
1104 0 : return TALER_MHD_reply_with_error (
1105 : connection,
1106 : MHD_HTTP_FORBIDDEN,
1107 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
1108 : NULL);
1109 : }
1110 :
1111 : }
1112 :
1113 : }
1114 :
1115 0 : if (contract_available)
1116 : {
1117 0 : god->claimed = true;
1118 : }
1119 : else
1120 : {
1121 : struct TALER_ClaimTokenP db_claim_token;
1122 : struct TALER_MerchantPostDataHashP unused;
1123 :
1124 0 : qs = TMH_db->lookup_order (TMH_db->cls,
1125 0 : hc->instance->settings.id,
1126 : order_id,
1127 : &db_claim_token,
1128 : &unused,
1129 0 : (NULL == god->contract_terms)
1130 : ? &god->contract_terms
1131 : : NULL);
1132 0 : if (0 > qs)
1133 : {
1134 : /* single, read-only SQL statements should never cause
1135 : serialization problems */
1136 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
1137 : /* Always report on hard error as well to enable diagnostics */
1138 0 : GNUNET_break (0);
1139 0 : return TALER_MHD_reply_with_error (connection,
1140 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1141 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1142 : "lookup_order");
1143 : }
1144 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1145 : {
1146 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1147 : "Unknown order id given: `%s'\n",
1148 : order_id);
1149 0 : return TALER_MHD_reply_with_error (connection,
1150 : MHD_HTTP_NOT_FOUND,
1151 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
1152 : order_id);
1153 : }
1154 : /* Note: when "!ord.requireClaimToken" and the client does not provide
1155 : a claim token (all zeros!), then token_match==TRUE below: */
1156 0 : token_match = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
1157 0 : (0 == GNUNET_memcmp (&db_claim_token,
1158 : &god->claim_token));
1159 : } /* end unclaimed order logic */
1160 :
1161 0 : GNUNET_assert (NULL != god->contract_terms);
1162 0 : merchant_base_url = json_string_value (json_object_get (god->contract_terms,
1163 : "merchant_base_url"));
1164 0 : if (NULL == merchant_base_url)
1165 : {
1166 0 : GNUNET_break (0);
1167 0 : return TALER_MHD_reply_with_error (connection,
1168 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1169 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1170 : order_id);
1171 : }
1172 :
1173 0 : if (NULL == god->fulfillment_url)
1174 0 : god->fulfillment_url = json_string_value (json_object_get (
1175 0 : god->contract_terms,
1176 : "fulfillment_url"));
1177 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1178 : "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
1179 : token_match,
1180 : contract_available,
1181 : contract_match,
1182 : god->claimed);
1183 :
1184 0 : if (claim_token_provided && ! token_match)
1185 : {
1186 : /* Authentication provided but wrong. */
1187 0 : GNUNET_break_op (0);
1188 0 : return TALER_MHD_reply_with_error (connection,
1189 : MHD_HTTP_FORBIDDEN,
1190 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
1191 : "authentication with claim token provided but wrong");
1192 : }
1193 :
1194 0 : if (h_contract_provided && ! contract_match)
1195 : {
1196 : /* Authentication provided but wrong. */
1197 0 : GNUNET_break_op (0);
1198 : /* FIXME: use better error code */
1199 0 : return TALER_MHD_reply_with_error (connection,
1200 : MHD_HTTP_FORBIDDEN,
1201 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
1202 : "authentication with h_contract provided but wrong");
1203 : }
1204 :
1205 0 : if (! (token_match ||
1206 : contract_match) )
1207 : {
1208 : const char *public_reorder_url;
1209 :
1210 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1211 : "Neither claim token nor contract matched\n");
1212 0 : public_reorder_url = json_string_value (json_object_get (
1213 0 : god->contract_terms,
1214 : "public_reorder_url"));
1215 : /* Client has no rights to this order */
1216 0 : if (NULL == public_reorder_url)
1217 : {
1218 : /* We cannot give the client a new order, just fail */
1219 0 : if (! GNUNET_is_zero (&god->h_contract_terms))
1220 : {
1221 0 : GNUNET_break_op (0);
1222 0 : return TALER_MHD_reply_with_error (
1223 : connection,
1224 : MHD_HTTP_FORBIDDEN,
1225 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
1226 : NULL);
1227 : }
1228 0 : return TALER_MHD_reply_with_error (connection,
1229 : MHD_HTTP_FORBIDDEN,
1230 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
1231 : "no 'public_reorder_url'");
1232 : }
1233 : /* We have a fulfillment URL, redirect the client there, maybe
1234 : the frontend can generate a fresh order for this new customer */
1235 0 : if (god->generate_html)
1236 : {
1237 : /* Contract was claimed (maybe by another device), so this client
1238 : cannot get the status information. Redirect to fulfillment page,
1239 : where the client may be able to pickup a fresh order -- or might
1240 : be able authenticate via session ID */
1241 : struct MHD_Response *reply;
1242 : MHD_RESULT ret;
1243 :
1244 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1245 : "Contract claimed, redirecting to fulfillment page for order %s\n",
1246 : order_id);
1247 0 : reply = MHD_create_response_from_buffer (0,
1248 : NULL,
1249 : MHD_RESPMEM_PERSISTENT);
1250 0 : if (NULL == reply)
1251 : {
1252 0 : GNUNET_break (0);
1253 0 : return MHD_NO;
1254 : }
1255 0 : GNUNET_break (MHD_YES ==
1256 : MHD_add_response_header (reply,
1257 : MHD_HTTP_HEADER_LOCATION,
1258 : public_reorder_url));
1259 0 : ret = MHD_queue_response (connection,
1260 : MHD_HTTP_FOUND,
1261 : reply);
1262 0 : MHD_destroy_response (reply);
1263 0 : return ret;
1264 : }
1265 : /* Need to generate JSON reply */
1266 0 : return TALER_MHD_REPLY_JSON_PACK (
1267 : connection,
1268 : MHD_HTTP_ACCEPTED,
1269 : GNUNET_JSON_pack_string ("public_reorder_url",
1270 : public_reorder_url));
1271 : }
1272 :
1273 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1274 : "Claim token or contract matched\n");
1275 :
1276 0 : if ( (NULL != god->session_id) &&
1277 0 : (NULL != god->fulfillment_url) )
1278 : {
1279 : /* Check if client paid for this fulfillment article
1280 : already within this session, but using a different
1281 : order ID. If so, redirect the client to the order
1282 : it already paid. Allows, for example, the case
1283 : where a mobile phone pays for a browser's session,
1284 : where the mobile phone has a different order
1285 : ID (because it purchased the article earlier)
1286 : than the one that the browser is waiting for. */
1287 0 : char *already_paid_order_id = NULL;
1288 :
1289 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1290 : "Running re-purchase detection for %s/%s\n",
1291 : god->session_id,
1292 : god->fulfillment_url);
1293 0 : qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
1294 0 : hc->instance->settings.id,
1295 : god->fulfillment_url,
1296 : god->session_id,
1297 : &already_paid_order_id);
1298 0 : if (qs < 0)
1299 : {
1300 : /* single, read-only SQL statements should never cause
1301 : serialization problems */
1302 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
1303 : /* Always report on hard error as well to enable diagnostics */
1304 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
1305 0 : return TALER_MHD_reply_with_error (connection,
1306 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1307 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1308 : "order by fulfillment");
1309 : }
1310 0 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
1311 0 : (0 != strcmp (order_id,
1312 : already_paid_order_id)) )
1313 : {
1314 : MHD_RESULT ret;
1315 :
1316 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1317 : "Sending pay request for order %s (already paid: %s)\n",
1318 : order_id,
1319 : already_paid_order_id);
1320 0 : ret = send_pay_request (god,
1321 : already_paid_order_id);
1322 0 : GNUNET_free (already_paid_order_id);
1323 0 : return ret;
1324 : }
1325 0 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1326 0 : GNUNET_free (already_paid_order_id);
1327 : }
1328 :
1329 0 : if (! god->claimed)
1330 : {
1331 : /* Order is unclaimed, no need to check for payments or even
1332 : refunds, simply always generate payment request */
1333 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1334 : "Order unclaimed, sending pay request for order %s\n",
1335 : order_id);
1336 0 : return send_pay_request (god,
1337 : NULL);
1338 : }
1339 :
1340 : {
1341 : /* Check if paid. */
1342 : struct TALER_PrivateContractHashP h_contract;
1343 : bool paid;
1344 :
1345 0 : qs = TMH_db->lookup_order_status (TMH_db->cls,
1346 0 : hc->instance->settings.id,
1347 : order_id,
1348 : &h_contract,
1349 : &paid);
1350 0 : if (0 >= qs)
1351 : {
1352 : /* Always report on hard error as well to enable diagnostics */
1353 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
1354 0 : return TALER_MHD_reply_with_error (connection,
1355 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1356 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1357 : "lookup_order_status");
1358 : }
1359 0 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
1360 0 : if (! paid)
1361 : {
1362 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1363 : "Order claimed but unpaid, sending pay request for order %s\n",
1364 : order_id);
1365 0 : return send_pay_request (god,
1366 : NULL);
1367 : }
1368 : }
1369 :
1370 : /* At this point, we know the contract was paid. Let's check for
1371 : refunds. First, clear away refunds found from previous invocations. */
1372 0 : GNUNET_assert (GNUNET_OK ==
1373 : TALER_amount_set_zero (TMH_currency,
1374 : &god->refund_amount));
1375 0 : GNUNET_assert (GNUNET_OK ==
1376 : TALER_amount_set_zero (TMH_currency,
1377 : &god->refund_taken));
1378 0 : qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
1379 0 : hc->instance->settings.id,
1380 0 : &god->h_contract_terms,
1381 : &process_refunds_cb,
1382 : god);
1383 0 : if (0 > qs)
1384 : {
1385 0 : GNUNET_break (0);
1386 0 : return TALER_MHD_reply_with_error (connection,
1387 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1388 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1389 : "lookup_refunds_detailed");
1390 : }
1391 :
1392 0 : if ( ((god->sc.awaiting_refund) &&
1393 0 : ( (! god->refunded) ||
1394 0 : (1 != TALER_amount_cmp (&god->refund_amount,
1395 0 : &god->sc.refund_expected)) )) ||
1396 0 : ( (god->sc.awaiting_refund_obtained) &&
1397 0 : (god->refund_pending) ) )
1398 : {
1399 : /* Client is waiting for a refund larger than what we have, suspend
1400 : until timeout */
1401 : struct GNUNET_TIME_Relative remaining;
1402 :
1403 0 : remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
1404 0 : if (! GNUNET_TIME_relative_is_zero (remaining))
1405 : {
1406 : /* yes, indeed suspend */
1407 0 : if (god->sc.awaiting_refund)
1408 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1409 : "Awaiting refund exceeding %s\n",
1410 : TALER_amount2s (&god->sc.refund_expected));
1411 0 : if (god->sc.awaiting_refund_obtained)
1412 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1413 : "Awaiting pending refunds\n");
1414 0 : suspend_god (god);
1415 0 : return MHD_YES;
1416 : }
1417 : }
1418 :
1419 : /* All operations done, build final response */
1420 0 : if (god->generate_html)
1421 : {
1422 : enum GNUNET_GenericReturnValue res;
1423 :
1424 0 : if (god->refund_pending)
1425 : {
1426 : char *qr;
1427 : char *uri;
1428 :
1429 0 : GNUNET_assert (NULL != god->contract_terms);
1430 0 : uri = make_taler_refund_uri (merchant_base_url,
1431 : order_id);
1432 0 : if (NULL == uri)
1433 : {
1434 0 : GNUNET_break (0);
1435 0 : return TALER_MHD_reply_with_error (god->sc.con,
1436 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1437 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1438 : "refund URI");
1439 : }
1440 0 : qr = TMH_create_qrcode (uri);
1441 0 : if (NULL == qr)
1442 : {
1443 0 : GNUNET_break (0);
1444 0 : GNUNET_free (uri);
1445 0 : return TALER_MHD_reply_with_error (god->sc.con,
1446 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1447 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
1448 : "qr code");
1449 : }
1450 : {
1451 : json_t *context;
1452 :
1453 0 : context = GNUNET_JSON_PACK (
1454 : GNUNET_JSON_pack_string ("order_summary",
1455 : get_order_summary (god)),
1456 : TALER_JSON_pack_amount ("refund_amount",
1457 : &god->refund_amount),
1458 : TALER_JSON_pack_amount ("refund_taken",
1459 : &god->refund_taken),
1460 : GNUNET_JSON_pack_string ("taler_refund_uri",
1461 : uri),
1462 : GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
1463 : qr));
1464 0 : res = TMH_return_from_template (god->sc.con,
1465 : MHD_HTTP_OK,
1466 : "offer_refund",
1467 0 : hc->instance->settings.id,
1468 : uri,
1469 : context);
1470 0 : json_decref (context);
1471 : }
1472 0 : GNUNET_free (uri);
1473 0 : GNUNET_free (qr);
1474 : }
1475 : else
1476 : {
1477 : json_t *context;
1478 :
1479 0 : context = GNUNET_JSON_PACK (
1480 : GNUNET_JSON_pack_object_incref ("contract_terms",
1481 : god->contract_terms),
1482 : GNUNET_JSON_pack_string ("order_summary",
1483 : get_order_summary (god)),
1484 : TALER_JSON_pack_amount ("refund_amount",
1485 : &god->refund_amount),
1486 : TALER_JSON_pack_amount ("refund_taken",
1487 : &god->refund_taken));
1488 0 : res = TMH_return_from_template (god->sc.con,
1489 : MHD_HTTP_OK,
1490 : "show_order_details",
1491 0 : hc->instance->settings.id,
1492 : NULL,
1493 : context);
1494 0 : json_decref (context);
1495 : }
1496 0 : if (GNUNET_SYSERR == res)
1497 : {
1498 0 : GNUNET_break (0);
1499 0 : return MHD_NO;
1500 : }
1501 0 : return MHD_YES;
1502 : }
1503 0 : return TALER_MHD_REPLY_JSON_PACK (
1504 : connection,
1505 : MHD_HTTP_OK,
1506 : GNUNET_JSON_pack_allow_null (
1507 : GNUNET_JSON_pack_string ("fulfillment_url",
1508 : god->fulfillment_url)),
1509 : GNUNET_JSON_pack_bool ("refunded",
1510 : god->refunded),
1511 : GNUNET_JSON_pack_bool ("refund_pending",
1512 : god->refund_pending),
1513 : TALER_JSON_pack_amount ("refund_taken",
1514 : &god->refund_taken),
1515 : TALER_JSON_pack_amount ("refund_amount",
1516 : &god->refund_amount));
1517 : }
1518 :
1519 :
1520 : /* end of taler-merchant-httpd_get-orders-ID.c */
|