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