Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2017-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 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_private-get-orders-ID.c
18 : * @brief implementation of GET /private/orders/ID handler
19 : * @author Florian Dold
20 : * @author Christian Grothoff
21 : * @author Bohdan Potuzhnyi
22 : * @author Iván Ávalos
23 : */
24 : #include "platform.h"
25 : #include <taler/taler_json_lib.h>
26 : #include <taler/taler_dbevents.h>
27 : #include <taler/taler_error_codes.h>
28 : #include <taler/taler_util.h>
29 : #include <gnunet/gnunet_common.h>
30 : #include <gnunet/gnunet_json_lib.h>
31 : #include "taler_merchant_util.h"
32 : #include "taler-merchant-httpd_helper.h"
33 : #include "taler-merchant-httpd_private-get-orders.h"
34 : #include "taler-merchant-httpd_private-get-orders-ID.h"
35 :
36 : /**
37 : * Data structure we keep for a check payment request.
38 : */
39 : struct GetOrderRequestContext;
40 :
41 :
42 : /**
43 : * Request to an exchange for details about wire transfers
44 : * in response to a coin's deposit operation.
45 : */
46 : struct TransferQuery
47 : {
48 :
49 : /**
50 : * Kept in a DLL.
51 : */
52 : struct TransferQuery *next;
53 :
54 : /**
55 : * Kept in a DLL.
56 : */
57 : struct TransferQuery *prev;
58 :
59 : /**
60 : * Base URL of the exchange.
61 : */
62 : char *exchange_url;
63 :
64 : /**
65 : * Overall request this TQ belongs with.
66 : */
67 : struct GetOrderRequestContext *gorc;
68 :
69 : /**
70 : * Hash of the merchant's bank account the transfer (presumably) went to.
71 : */
72 : struct TALER_MerchantWireHashP h_wire;
73 :
74 : /**
75 : * Value deposited (including deposit fee).
76 : */
77 : struct TALER_Amount amount_with_fee;
78 :
79 : /**
80 : * Deposit fee paid for this coin.
81 : */
82 : struct TALER_Amount deposit_fee;
83 :
84 : /**
85 : * Public key of the coin this is about.
86 : */
87 : struct TALER_CoinSpendPublicKeyP coin_pub;
88 :
89 : /**
90 : * Which deposit operation is this about?
91 : */
92 : uint64_t deposit_serial;
93 :
94 : };
95 :
96 :
97 : /**
98 : * Phases of order processing.
99 : */
100 : enum GetOrderPhase
101 : {
102 : /**
103 : * Initialization.
104 : */
105 : GOP_INIT = 0,
106 :
107 : /**
108 : * Obtain contract terms from database.
109 : */
110 : GOP_FETCH_CONTRACT = 1,
111 :
112 : /**
113 : * Parse the contract terms.
114 : */
115 : GOP_PARSE_CONTRACT = 2,
116 :
117 : /**
118 : * Check if the contract was fully paid.
119 : */
120 : GOP_CHECK_PAID = 3,
121 :
122 : /**
123 : * Check if the wallet may have purchased an equivalent
124 : * order before and we need to redirect the wallet to
125 : * an existing paid order.
126 : */
127 : GOP_CHECK_REPURCHASE = 4,
128 :
129 : /**
130 : * Terminate processing of unpaid orders, either by
131 : * suspending until payment or by returning the
132 : * unpaid order status.
133 : */
134 : GOP_UNPAID_FINISH = 5,
135 :
136 : /**
137 : * Check if the (paid) order was refunded.
138 : */
139 : GOP_CHECK_REFUNDS = 6,
140 :
141 : /**
142 : * Load all deposits associated with the order.
143 : */
144 : GOP_CHECK_DEPOSITS = 7,
145 :
146 : /**
147 : * Check local records for transfers of funds to
148 : * the merchant.
149 : */
150 : GOP_CHECK_LOCAL_TRANSFERS = 8,
151 :
152 : /**
153 : * Generate final comprehensive result.
154 : */
155 : GOP_REPLY_RESULT = 9,
156 :
157 : /**
158 : * End with the HTTP status and error code in
159 : * wire_hc and wire_ec.
160 : */
161 : GOP_ERROR = 10,
162 :
163 : /**
164 : * We are suspended awaiting payment.
165 : */
166 : GOP_SUSPENDED_ON_UNPAID = 11,
167 :
168 : /**
169 : * Processing is done, return #MHD_YES.
170 : */
171 : GOP_END_YES = 12,
172 :
173 : /**
174 : * Processing is done, return #MHD_NO.
175 : */
176 : GOP_END_NO = 13
177 :
178 : };
179 :
180 :
181 : /**
182 : * Data structure we keep for a check payment request.
183 : */
184 : struct GetOrderRequestContext
185 : {
186 :
187 : /**
188 : * Processing phase we are in.
189 : */
190 : enum GetOrderPhase phase;
191 :
192 : /**
193 : * Entry in the #resume_timeout_heap for this check payment, if we are
194 : * suspended.
195 : */
196 : struct TMH_SuspendedConnection sc;
197 :
198 : /**
199 : * Which merchant instance is this for?
200 : */
201 : struct TMH_HandlerContext *hc;
202 :
203 : /**
204 : * session of the client
205 : */
206 : const char *session_id;
207 :
208 : /**
209 : * Kept in a DLL while suspended on exchange.
210 : */
211 : struct GetOrderRequestContext *next;
212 :
213 : /**
214 : * Kept in a DLL while suspended on exchange.
215 : */
216 : struct GetOrderRequestContext *prev;
217 :
218 : /**
219 : * Head of DLL of individual queries for transfer data.
220 : */
221 : struct TransferQuery *tq_head;
222 :
223 : /**
224 : * Tail of DLL of individual queries for transfer data.
225 : */
226 : struct TransferQuery *tq_tail;
227 :
228 : /**
229 : * Timeout task while waiting on exchange.
230 : */
231 : struct GNUNET_SCHEDULER_Task *tt;
232 :
233 : /**
234 : * Database event we are waiting on to be resuming
235 : * for payment or refunds.
236 : */
237 : struct GNUNET_DB_EventHandler *eh;
238 :
239 : /**
240 : * Database event we are waiting on to be resuming
241 : * for session capture.
242 : */
243 : struct GNUNET_DB_EventHandler *session_eh;
244 :
245 : /**
246 : * Contract terms of the payment we are checking. NULL when they
247 : * are not (yet) known.
248 : */
249 : json_t *contract_terms_json;
250 :
251 : /**
252 : * Parsed contract terms, NULL when parsing failed
253 : */
254 : struct TALER_MERCHANT_Contract *contract_terms;
255 :
256 : /**
257 : * Claim token of the order.
258 : */
259 : struct TALER_ClaimTokenP claim_token;
260 :
261 : /**
262 : * Timestamp of the last payment.
263 : */
264 : struct GNUNET_TIME_Timestamp last_payment;
265 :
266 : /**
267 : * Wire details for the payment, to be returned in the reply. NULL
268 : * if not available.
269 : */
270 : json_t *wire_details;
271 :
272 : /**
273 : * Details about refunds, NULL if there are no refunds.
274 : */
275 : json_t *refund_details;
276 :
277 : /**
278 : * Amount of the order, unset for unpaid v1 orders.
279 : */
280 : struct TALER_Amount contract_amount;
281 :
282 : /**
283 : * Hash over the @e contract_terms.
284 : */
285 : struct TALER_PrivateContractHashP h_contract_terms;
286 :
287 : /**
288 : * Total amount the exchange deposited into our bank account
289 : * (confirmed or unconfirmed), excluding fees.
290 : */
291 : struct TALER_Amount deposits_total;
292 :
293 : /**
294 : * Total amount in deposit fees we paid for all coins.
295 : */
296 : struct TALER_Amount deposit_fees_total;
297 :
298 : /**
299 : * Total value of the coins that the exchange deposited into our bank
300 : * account (confirmed or unconfirmed), including deposit fees.
301 : */
302 : struct TALER_Amount value_total;
303 :
304 : /**
305 : * Serial ID of the order.
306 : */
307 : uint64_t order_serial;
308 :
309 : /**
310 : * Index of selected choice from ``choices`` array in the contract_terms.
311 : * Is -1 for orders without choices.
312 : */
313 : int16_t choice_index;
314 :
315 : /**
316 : * Total refunds granted for this payment. Only initialized
317 : * if @e refunded is set to true.
318 : */
319 : struct TALER_Amount refund_amount;
320 :
321 : /**
322 : * Exchange HTTP error code encountered while trying to determine wire transfer
323 : * details. #TALER_EC_NONE for no error encountered.
324 : */
325 : unsigned int exchange_hc;
326 :
327 : /**
328 : * Exchange error code encountered while trying to determine wire transfer
329 : * details. #TALER_EC_NONE for no error encountered.
330 : */
331 : enum TALER_ErrorCode exchange_ec;
332 :
333 : /**
334 : * Error code encountered while trying to determine wire transfer
335 : * details. #TALER_EC_NONE for no error encountered.
336 : */
337 : enum TALER_ErrorCode wire_ec;
338 :
339 : /**
340 : * Set to YES if refunded orders should be included when
341 : * doing repurchase detection.
342 : */
343 : enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
344 :
345 : /**
346 : * HTTP status to return with @e wire_ec, 0 if @e wire_ec is #TALER_EC_NONE.
347 : */
348 : unsigned int wire_hc;
349 :
350 : /**
351 : * Did we suspend @a connection and are thus in
352 : * the #gorc_head DLL (#GNUNET_YES). Set to
353 : * #GNUNET_NO if we are not suspended, and to
354 : * #GNUNET_SYSERR if we should close the connection
355 : * without a response due to shutdown.
356 : */
357 : enum GNUNET_GenericReturnValue suspended;
358 :
359 : /**
360 : * Set to true if this payment has been refunded and
361 : * @e refund_amount is initialized.
362 : */
363 : bool refunded;
364 :
365 : /**
366 : * True if the order was paid.
367 : */
368 : bool paid;
369 :
370 : /**
371 : * True if the paid session in the database matches
372 : * our @e session_id.
373 : */
374 : bool paid_session_matches;
375 :
376 : /**
377 : * True if the exchange wired the money to the merchant.
378 : */
379 : bool wired;
380 :
381 : /**
382 : * True if the order remains unclaimed.
383 : */
384 : bool order_only;
385 :
386 : /**
387 : * Set to true if this payment has been refunded and
388 : * some refunds remain to be picked up by the wallet.
389 : */
390 : bool refund_pending;
391 :
392 : /**
393 : * Set to true if our database (incorrectly) has refunds
394 : * in a different currency than the currency of the
395 : * original payment for the order.
396 : */
397 : bool refund_currency_mismatch;
398 :
399 : /**
400 : * Set to true if our database (incorrectly) has deposits
401 : * in a different currency than the currency of the
402 : * original payment for the order.
403 : */
404 : bool deposit_currency_mismatch;
405 : };
406 :
407 :
408 : /**
409 : * Head of list of suspended requests waiting on the exchange.
410 : */
411 : static struct GetOrderRequestContext *gorc_head;
412 :
413 : /**
414 : * Tail of list of suspended requests waiting on the exchange.
415 : */
416 : static struct GetOrderRequestContext *gorc_tail;
417 :
418 :
419 : void
420 14 : TMH_force_gorc_resume (void)
421 : {
422 : struct GetOrderRequestContext *gorc;
423 :
424 14 : while (NULL != (gorc = gorc_head))
425 : {
426 0 : GNUNET_CONTAINER_DLL_remove (gorc_head,
427 : gorc_tail,
428 : gorc);
429 0 : GNUNET_assert (GNUNET_YES == gorc->suspended);
430 0 : gorc->suspended = GNUNET_SYSERR;
431 0 : MHD_resume_connection (gorc->sc.con);
432 : }
433 14 : }
434 :
435 :
436 : /**
437 : * We have received a trigger from the database
438 : * that we should (possibly) resume the request.
439 : *
440 : * @param cls a `struct GetOrderRequestContext` to resume
441 : * @param extra string encoding refund amount (or NULL)
442 : * @param extra_size number of bytes in @a extra
443 : */
444 : static void
445 2 : resume_by_event (void *cls,
446 : const void *extra,
447 : size_t extra_size)
448 : {
449 2 : struct GetOrderRequestContext *gorc = cls;
450 :
451 : (void) extra;
452 : (void) extra_size;
453 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
454 : "Resuming request for order %s by trigger\n",
455 : gorc->hc->infix);
456 2 : if (GNUNET_NO == gorc->suspended)
457 0 : return; /* duplicate event is possible */
458 2 : gorc->suspended = GNUNET_NO;
459 2 : gorc->phase = GOP_FETCH_CONTRACT;
460 2 : GNUNET_CONTAINER_DLL_remove (gorc_head,
461 : gorc_tail,
462 : gorc);
463 2 : MHD_resume_connection (gorc->sc.con);
464 2 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
465 : }
466 :
467 :
468 : /**
469 : * Clean up the session state for a GET /private/order/ID request.
470 : *
471 : * @param cls closure, must be a `struct GetOrderRequestContext *`
472 : */
473 : static void
474 59 : gorc_cleanup (void *cls)
475 : {
476 59 : struct GetOrderRequestContext *gorc = cls;
477 : struct TransferQuery *tq;
478 :
479 93 : while (NULL != (tq = gorc->tq_head))
480 : {
481 34 : GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
482 : gorc->tq_tail,
483 : tq);
484 34 : GNUNET_free (tq->exchange_url);
485 34 : GNUNET_free (tq);
486 : }
487 :
488 59 : if (NULL != gorc->contract_terms_json)
489 59 : json_decref (gorc->contract_terms_json);
490 59 : if (NULL != gorc->contract_terms)
491 : {
492 59 : TALER_MERCHANT_contract_free (gorc->contract_terms);
493 59 : gorc->contract_terms = NULL;
494 : }
495 59 : if (NULL != gorc->wire_details)
496 25 : json_decref (gorc->wire_details);
497 59 : if (NULL != gorc->refund_details)
498 25 : json_decref (gorc->refund_details);
499 59 : if (NULL != gorc->tt)
500 : {
501 0 : GNUNET_SCHEDULER_cancel (gorc->tt);
502 0 : gorc->tt = NULL;
503 : }
504 59 : if (NULL != gorc->eh)
505 : {
506 4 : TMH_db->event_listen_cancel (gorc->eh);
507 4 : gorc->eh = NULL;
508 : }
509 59 : if (NULL != gorc->session_eh)
510 : {
511 0 : TMH_db->event_listen_cancel (gorc->session_eh);
512 0 : gorc->session_eh = NULL;
513 : }
514 59 : GNUNET_free (gorc);
515 59 : }
516 :
517 :
518 : /**
519 : * Processing the request @a gorc is finished, set the
520 : * final return value in phase based on @a mret.
521 : *
522 : * @param[in,out] gorc order context to initialize
523 : * @param mret MHD HTTP response status to return
524 : */
525 : static void
526 59 : phase_end (struct GetOrderRequestContext *gorc,
527 : MHD_RESULT mret)
528 : {
529 59 : gorc->phase = (MHD_YES == mret)
530 : ? GOP_END_YES
531 59 : : GOP_END_NO;
532 59 : }
533 :
534 :
535 : /**
536 : * Initialize event callbacks for the order processing.
537 : *
538 : * @param[in,out] gorc order context to initialize
539 : */
540 : static void
541 59 : phase_init (struct GetOrderRequestContext *gorc)
542 : {
543 59 : struct TMH_HandlerContext *hc = gorc->hc;
544 59 : struct TMH_OrderPayEventP pay_eh = {
545 59 : .header.size = htons (sizeof (pay_eh)),
546 59 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
547 59 : .merchant_pub = hc->instance->merchant_pub
548 : };
549 :
550 59 : if (! GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
551 : {
552 55 : gorc->phase++;
553 55 : return;
554 : }
555 :
556 4 : GNUNET_CRYPTO_hash (hc->infix,
557 4 : strlen (hc->infix),
558 : &pay_eh.h_order_id);
559 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
560 : "Subscribing to payment triggers for %p\n",
561 : gorc);
562 4 : gorc->eh = TMH_db->event_listen (
563 4 : TMH_db->cls,
564 : &pay_eh.header,
565 : GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
566 : &resume_by_event,
567 : gorc);
568 4 : if ( (NULL != gorc->session_id) &&
569 0 : (NULL != gorc->contract_terms->fulfillment_url) )
570 : {
571 0 : struct TMH_SessionEventP session_eh = {
572 0 : .header.size = htons (sizeof (session_eh)),
573 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
574 0 : .merchant_pub = hc->instance->merchant_pub
575 : };
576 :
577 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
578 : "Subscribing to session triggers for %p\n",
579 : gorc);
580 0 : GNUNET_CRYPTO_hash (gorc->session_id,
581 : strlen (gorc->session_id),
582 : &session_eh.h_session_id);
583 0 : GNUNET_CRYPTO_hash (gorc->contract_terms->fulfillment_url,
584 0 : strlen (gorc->contract_terms->fulfillment_url),
585 : &session_eh.h_fulfillment_url);
586 : gorc->session_eh
587 0 : = TMH_db->event_listen (
588 0 : TMH_db->cls,
589 : &session_eh.header,
590 : GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
591 : &resume_by_event,
592 : gorc);
593 : }
594 4 : gorc->phase++;
595 : }
596 :
597 :
598 : /**
599 : * Obtain latest contract terms from the database.
600 : *
601 : * @param[in,out] gorc order context to update
602 : */
603 : static void
604 61 : phase_fetch_contract (struct GetOrderRequestContext *gorc)
605 : {
606 61 : struct TMH_HandlerContext *hc = gorc->hc;
607 : enum GNUNET_DB_QueryStatus qs;
608 :
609 61 : if (NULL != gorc->contract_terms_json)
610 : {
611 : /* Free memory filled with old contract terms before fetching the latest
612 : ones from the DB. Note that we cannot simply skip the database
613 : interaction as the contract terms loaded previously might be from an
614 : earlier *unclaimed* order state (which we loaded in a previous
615 : invocation of this function and we are back here due to long polling)
616 : and thus the contract terms could have changed during claiming. Thus,
617 : we need to fetch the latest contract terms from the DB again. */
618 2 : json_decref (gorc->contract_terms_json);
619 2 : gorc->contract_terms_json = NULL;
620 2 : gorc->order_only = false;
621 : }
622 61 : TMH_db->preflight (TMH_db->cls);
623 61 : qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
624 61 : hc->instance->settings.id,
625 61 : hc->infix,
626 : gorc->session_id,
627 : &gorc->contract_terms_json,
628 : &gorc->order_serial,
629 : &gorc->paid,
630 : &gorc->wired,
631 : &gorc->paid_session_matches,
632 : &gorc->claim_token,
633 : &gorc->choice_index);
634 61 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
635 : "lookup_contract_terms (%s) returned %d\n",
636 : hc->infix,
637 : (int) qs);
638 61 : if (0 > qs)
639 : {
640 : /* single, read-only SQL statements should never cause
641 : serialization problems */
642 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
643 : /* Always report on hard error as well to enable diagnostics */
644 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
645 0 : phase_end (gorc,
646 : TALER_MHD_reply_with_error (gorc->sc.con,
647 : MHD_HTTP_INTERNAL_SERVER_ERROR,
648 : TALER_EC_GENERIC_DB_FETCH_FAILED,
649 : "contract terms"));
650 0 : return;
651 : }
652 61 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
653 : {
654 49 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
655 : "Order %s is %s (%s) according to database\n",
656 : hc->infix,
657 : gorc->paid ? "paid" : "unpaid",
658 : gorc->wired ? "wired" : "unwired");
659 49 : gorc->phase++;
660 49 : return;
661 : }
662 12 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
663 12 : GNUNET_assert (! gorc->paid);
664 : /* No contract, only order, fetch from orders table */
665 12 : gorc->order_only = true;
666 : {
667 : struct TALER_MerchantPostDataHashP unused;
668 :
669 : /* We need the order for two cases: Either when the contract doesn't exist yet,
670 : * or when the order is claimed but unpaid, and we need the claim token. */
671 12 : qs = TMH_db->lookup_order (TMH_db->cls,
672 12 : hc->instance->settings.id,
673 12 : hc->infix,
674 : &gorc->claim_token,
675 : &unused,
676 : &gorc->contract_terms_json);
677 : }
678 12 : if (0 > qs)
679 : {
680 : /* single, read-only SQL statements should never cause
681 : serialization problems */
682 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
683 : /* Always report on hard error as well to enable diagnostics */
684 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
685 0 : phase_end (gorc,
686 : TALER_MHD_reply_with_error (gorc->sc.con,
687 : MHD_HTTP_INTERNAL_SERVER_ERROR,
688 : TALER_EC_GENERIC_DB_FETCH_FAILED,
689 : "order"));
690 0 : return;
691 : }
692 12 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
693 : {
694 0 : phase_end (gorc,
695 : TALER_MHD_reply_with_error (gorc->sc.con,
696 : MHD_HTTP_NOT_FOUND,
697 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
698 0 : hc->infix));
699 0 : return;
700 : }
701 12 : gorc->phase++;
702 : }
703 :
704 :
705 : /**
706 : * Obtain parse contract terms of the order. Extracts the fulfillment URL,
707 : * total amount, summary and timestamp from the contract terms!
708 : *
709 : * @param[in,out] gorc order context to update
710 : */
711 : static void
712 61 : phase_parse_contract (struct GetOrderRequestContext *gorc)
713 : {
714 61 : struct TMH_HandlerContext *hc = gorc->hc;
715 :
716 61 : if (NULL == gorc->contract_terms)
717 : {
718 59 : gorc->contract_terms = TALER_MERCHANT_contract_parse (
719 : gorc->contract_terms_json,
720 : true);
721 :
722 59 : if (NULL == gorc->contract_terms)
723 : {
724 0 : GNUNET_break (0);
725 0 : phase_end (gorc,
726 : TALER_MHD_reply_with_error (
727 : gorc->sc.con,
728 : MHD_HTTP_INTERNAL_SERVER_ERROR,
729 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
730 0 : hc->infix));
731 0 : return;
732 : }
733 : }
734 :
735 61 : switch (gorc->contract_terms->version)
736 : {
737 61 : case TALER_MERCHANT_CONTRACT_VERSION_0:
738 61 : gorc->contract_amount = gorc->contract_terms->details.v0.brutto;
739 61 : break;
740 0 : case TALER_MERCHANT_CONTRACT_VERSION_1:
741 0 : if (gorc->choice_index >= 0)
742 : {
743 0 : if (gorc->choice_index >=
744 0 : gorc->contract_terms->details.v1.choices_len)
745 : {
746 0 : GNUNET_break (0);
747 0 : phase_end (gorc,
748 : TALER_MHD_reply_with_error (
749 : gorc->sc.con, MHD_HTTP_INTERNAL_SERVER_ERROR,
750 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
751 : NULL));
752 0 : return;
753 : }
754 :
755 0 : gorc->contract_amount =
756 0 : gorc->contract_terms->details.v1.choices[gorc->choice_index].amount;
757 : }
758 : else
759 : {
760 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
761 : "choice index %i for order %s is invalid or not yet available",
762 : gorc->choice_index,
763 : gorc->contract_terms->order_id);
764 : }
765 0 : break;
766 0 : default:
767 : {
768 0 : GNUNET_break (0);
769 0 : phase_end (gorc,
770 : TALER_MHD_reply_with_error (
771 : gorc->sc.con,
772 : MHD_HTTP_INTERNAL_SERVER_ERROR,
773 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
774 : NULL));
775 0 : return;
776 : }
777 : }
778 :
779 110 : if (! gorc->order_only && GNUNET_OK !=
780 49 : TALER_JSON_contract_hash (gorc->contract_terms_json,
781 : &gorc->h_contract_terms))
782 : {
783 : {
784 0 : GNUNET_break (0);
785 0 : phase_end (gorc,
786 : TALER_MHD_reply_with_error (gorc->sc.con,
787 : MHD_HTTP_INTERNAL_SERVER_ERROR,
788 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
789 : NULL));
790 0 : return;
791 : }
792 : }
793 61 : GNUNET_assert (NULL != gorc->contract_terms_json);
794 61 : GNUNET_assert (NULL != gorc->contract_terms);
795 61 : gorc->phase++;
796 : }
797 :
798 :
799 : /**
800 : * Check payment status of the order.
801 : *
802 : * @param[in,out] gorc order context to update
803 : */
804 : static void
805 61 : phase_check_paid (struct GetOrderRequestContext *gorc)
806 : {
807 61 : struct TMH_HandlerContext *hc = gorc->hc;
808 :
809 61 : if (gorc->order_only)
810 : {
811 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
812 : "Order %s unclaimed, no need to lookup payment status\n",
813 : hc->infix);
814 12 : GNUNET_assert (! gorc->paid);
815 12 : GNUNET_assert (! gorc->wired);
816 12 : gorc->phase++;
817 12 : return;
818 : }
819 49 : if (NULL == gorc->session_id)
820 : {
821 39 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
822 : "No session ID, do not need to lookup session-ID specific payment status (%s/%s)\n",
823 : gorc->paid ? "paid" : "unpaid",
824 : gorc->wired ? "wired" : "unwired");
825 39 : gorc->phase++;
826 39 : return;
827 : }
828 10 : if (! gorc->paid_session_matches)
829 : {
830 8 : gorc->paid = false;
831 8 : gorc->wired = false;
832 : }
833 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
834 : "Order %s %s for session %s (%s)\n",
835 : hc->infix,
836 : gorc->paid ? "paid" : "unpaid",
837 : gorc->session_id,
838 : gorc->wired ? "wired" : "unwired");
839 10 : gorc->phase++;
840 : }
841 :
842 :
843 : /**
844 : * Check if re-purchase detection applies to the order.
845 : *
846 : * @param[in,out] gorc order context to update
847 : */
848 : static void
849 61 : phase_check_repurchase (struct GetOrderRequestContext *gorc)
850 : {
851 61 : struct TMH_HandlerContext *hc = gorc->hc;
852 61 : char *already_paid_order_id = NULL;
853 : enum GNUNET_DB_QueryStatus qs;
854 : char *taler_pay_uri;
855 : char *order_status_url;
856 : MHD_RESULT ret;
857 :
858 61 : if ( (gorc->paid) ||
859 27 : (NULL == gorc->contract_terms->fulfillment_url) ||
860 16 : (NULL == gorc->session_id) )
861 : {
862 : /* Repurchase cannot apply */
863 53 : gorc->phase++;
864 57 : return;
865 : }
866 8 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
867 : "Running re-purchase detection for %s/%s\n",
868 : gorc->session_id,
869 : gorc->contract_terms->fulfillment_url);
870 8 : qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
871 8 : hc->instance->settings.id,
872 8 : gorc->contract_terms->
873 : fulfillment_url,
874 : gorc->session_id,
875 : TALER_EXCHANGE_YNA_NO !=
876 8 : gorc->allow_refunded_for_repurchase,
877 : &already_paid_order_id);
878 8 : if (0 > qs)
879 : {
880 : /* single, read-only SQL statements should never cause
881 : serialization problems, and the entry should exist as per above */
882 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
883 0 : phase_end (gorc,
884 : TALER_MHD_reply_with_error (gorc->sc.con,
885 : MHD_HTTP_INTERNAL_SERVER_ERROR,
886 : TALER_EC_GENERIC_DB_FETCH_FAILED,
887 : "order by fulfillment"));
888 0 : return;
889 : }
890 8 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
891 : {
892 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
893 : "No already paid order for %s/%s\n",
894 : gorc->session_id,
895 : gorc->contract_terms->fulfillment_url);
896 4 : gorc->phase++;
897 4 : return;
898 : }
899 :
900 : /* User did pay for this order, but under a different session; ask wallet to
901 : switch order ID */
902 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
903 : "Found already paid order %s\n",
904 : already_paid_order_id);
905 4 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
906 4 : hc->infix,
907 : gorc->session_id,
908 4 : hc->instance->settings.id,
909 : &gorc->claim_token);
910 4 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
911 4 : hc->infix,
912 : gorc->session_id,
913 4 : hc->instance->settings.id,
914 : &gorc->claim_token,
915 : NULL);
916 4 : if ( (NULL == taler_pay_uri) ||
917 : (NULL == order_status_url) )
918 : {
919 0 : GNUNET_break_op (0);
920 0 : GNUNET_free (taler_pay_uri);
921 0 : GNUNET_free (order_status_url);
922 0 : phase_end (gorc,
923 : TALER_MHD_reply_with_error (gorc->sc.con,
924 : MHD_HTTP_BAD_REQUEST,
925 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
926 : "host"));
927 0 : return;
928 : }
929 4 : ret = TALER_MHD_REPLY_JSON_PACK (
930 : gorc->sc.con,
931 : MHD_HTTP_OK,
932 : GNUNET_JSON_pack_string ("taler_pay_uri",
933 : taler_pay_uri),
934 : GNUNET_JSON_pack_string ("order_status_url",
935 : order_status_url),
936 : GNUNET_JSON_pack_string ("order_status",
937 : "unpaid"),
938 : GNUNET_JSON_pack_string ("already_paid_order_id",
939 : already_paid_order_id),
940 : GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
941 : gorc->contract_terms->fulfillment_url),
942 : /* undefined for unpaid v1 contracts */
943 : GNUNET_JSON_pack_allow_null (
944 : TALER_JSON_pack_amount ("total_amount",
945 : &gorc->contract_amount)),
946 : GNUNET_JSON_pack_string ("summary",
947 : gorc->contract_terms->summary),
948 : GNUNET_JSON_pack_timestamp ("creation_time",
949 : gorc->contract_terms->timestamp));
950 4 : GNUNET_free (order_status_url);
951 4 : GNUNET_free (taler_pay_uri);
952 4 : GNUNET_free (already_paid_order_id);
953 4 : phase_end (gorc,
954 : ret);
955 : }
956 :
957 :
958 : /**
959 : * Check if we should suspend until the order is paid.
960 : *
961 : * @param[in,out] gorc order context to update
962 : */
963 : static void
964 57 : phase_unpaid_finish (struct GetOrderRequestContext *gorc)
965 : {
966 57 : struct TMH_HandlerContext *hc = gorc->hc;
967 : char *taler_pay_uri;
968 : char *order_status_url;
969 : MHD_RESULT ret;
970 :
971 57 : if (gorc->paid)
972 : {
973 34 : gorc->phase++;
974 45 : return;
975 : }
976 : /* User never paid for this order, suspend waiting
977 : on payment or return details. */
978 23 : if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
979 : {
980 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
981 : "Suspending GET /private/orders/%s\n",
982 : hc->infix);
983 2 : GNUNET_CONTAINER_DLL_insert (gorc_head,
984 : gorc_tail,
985 : gorc);
986 2 : gorc->phase = GOP_SUSPENDED_ON_UNPAID;
987 2 : gorc->suspended = GNUNET_YES;
988 2 : MHD_suspend_connection (gorc->sc.con);
989 2 : return;
990 : }
991 21 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
992 21 : hc->infix,
993 : gorc->session_id,
994 21 : hc->instance->settings.id,
995 : &gorc->claim_token,
996 : NULL);
997 21 : if (! gorc->order_only)
998 : {
999 9 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1000 : "Order %s claimed but not paid yet\n",
1001 : hc->infix);
1002 9 : phase_end (gorc,
1003 9 : TALER_MHD_REPLY_JSON_PACK (
1004 : gorc->sc.con,
1005 : MHD_HTTP_OK,
1006 : GNUNET_JSON_pack_string ("order_status_url",
1007 : order_status_url),
1008 : GNUNET_JSON_pack_object_incref ("contract_terms",
1009 : gorc->contract_terms_json),
1010 : GNUNET_JSON_pack_string ("order_status",
1011 : "claimed")));
1012 9 : GNUNET_free (order_status_url);
1013 9 : return;
1014 : }
1015 12 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
1016 12 : hc->infix,
1017 : gorc->session_id,
1018 12 : hc->instance->settings.id,
1019 : &gorc->claim_token);
1020 12 : ret = TALER_MHD_REPLY_JSON_PACK (
1021 : gorc->sc.con,
1022 : MHD_HTTP_OK,
1023 : GNUNET_JSON_pack_string ("taler_pay_uri",
1024 : taler_pay_uri),
1025 : GNUNET_JSON_pack_string ("order_status_url",
1026 : order_status_url),
1027 : GNUNET_JSON_pack_string ("order_status",
1028 : "unpaid"),
1029 : /* undefined for unpaid v1 contracts */
1030 : GNUNET_JSON_pack_allow_null (
1031 : TALER_JSON_pack_amount ("total_amount",
1032 : &gorc->contract_amount)),
1033 : GNUNET_JSON_pack_string ("summary",
1034 : gorc->contract_terms->summary),
1035 : GNUNET_JSON_pack_timestamp ("creation_time",
1036 : gorc->contract_terms->timestamp));
1037 12 : GNUNET_free (taler_pay_uri);
1038 12 : GNUNET_free (order_status_url);
1039 12 : phase_end (gorc,
1040 : ret);
1041 :
1042 : }
1043 :
1044 :
1045 : /**
1046 : * Function called with information about a refund.
1047 : * It is responsible for summing up the refund amount.
1048 : *
1049 : * @param cls closure
1050 : * @param refund_serial unique serial number of the refund
1051 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
1052 : * @param coin_pub public coin from which the refund comes from
1053 : * @param exchange_url URL of the exchange that issued @a coin_pub
1054 : * @param rtransaction_id identificator of the refund
1055 : * @param reason human-readable explanation of the refund
1056 : * @param refund_amount refund amount which is being taken from @a coin_pub
1057 : * @param pending true if the this refund was not yet processed by the wallet/exchange
1058 : */
1059 : static void
1060 8 : process_refunds_cb (
1061 : void *cls,
1062 : uint64_t refund_serial,
1063 : struct GNUNET_TIME_Timestamp timestamp,
1064 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
1065 : const char *exchange_url,
1066 : uint64_t rtransaction_id,
1067 : const char *reason,
1068 : const struct TALER_Amount *refund_amount,
1069 : bool pending)
1070 : {
1071 8 : struct GetOrderRequestContext *gorc = cls;
1072 :
1073 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1074 : "Found refund %llu over %s for reason %s\n",
1075 : (unsigned long long) rtransaction_id,
1076 : TALER_amount2s (refund_amount),
1077 : reason);
1078 8 : GNUNET_assert (
1079 : 0 ==
1080 : json_array_append_new (
1081 : gorc->refund_details,
1082 : GNUNET_JSON_PACK (
1083 : TALER_JSON_pack_amount ("amount",
1084 : refund_amount),
1085 : GNUNET_JSON_pack_bool ("pending",
1086 : pending),
1087 : GNUNET_JSON_pack_timestamp ("timestamp",
1088 : timestamp),
1089 : GNUNET_JSON_pack_string ("reason",
1090 : reason))));
1091 : /* For refunded coins, we are not charged deposit fees, so subtract those
1092 : again */
1093 8 : for (struct TransferQuery *tq = gorc->tq_head;
1094 8 : NULL != tq;
1095 0 : tq = tq->next)
1096 : {
1097 0 : if (0 !=
1098 0 : strcmp (exchange_url,
1099 0 : tq->exchange_url))
1100 0 : continue;
1101 0 : if (0 !=
1102 0 : GNUNET_memcmp (&tq->coin_pub,
1103 : coin_pub))
1104 0 : continue;
1105 0 : if (GNUNET_OK !=
1106 0 : TALER_amount_cmp_currency (
1107 0 : &gorc->deposit_fees_total,
1108 0 : &tq->deposit_fee))
1109 : {
1110 0 : gorc->refund_currency_mismatch = true;
1111 0 : return;
1112 : }
1113 0 : GNUNET_assert (
1114 : 0 <=
1115 : TALER_amount_subtract (&gorc->deposit_fees_total,
1116 : &gorc->deposit_fees_total,
1117 : &tq->deposit_fee));
1118 : }
1119 8 : if (GNUNET_OK !=
1120 8 : TALER_amount_cmp_currency (
1121 8 : &gorc->refund_amount,
1122 : refund_amount))
1123 : {
1124 0 : gorc->refund_currency_mismatch = true;
1125 0 : return;
1126 : }
1127 8 : GNUNET_assert (0 <=
1128 : TALER_amount_add (&gorc->refund_amount,
1129 : &gorc->refund_amount,
1130 : refund_amount));
1131 8 : gorc->refunded = true;
1132 8 : gorc->refund_pending |= pending;
1133 : }
1134 :
1135 :
1136 : /**
1137 : * Check refund status for the order.
1138 : *
1139 : * @param[in,out] gorc order context to update
1140 : */
1141 : static void
1142 34 : phase_check_refunds (struct GetOrderRequestContext *gorc)
1143 : {
1144 34 : struct TMH_HandlerContext *hc = gorc->hc;
1145 : enum GNUNET_DB_QueryStatus qs;
1146 :
1147 34 : GNUNET_assert (! gorc->order_only);
1148 34 : GNUNET_assert (gorc->paid);
1149 :
1150 : /* Accumulate refunds, if any. */
1151 34 : GNUNET_assert (GNUNET_OK ==
1152 : TALER_amount_set_zero (gorc->contract_amount.currency,
1153 : &gorc->refund_amount));
1154 :
1155 34 : qs = TMH_db->lookup_refunds_detailed (
1156 34 : TMH_db->cls,
1157 34 : hc->instance->settings.id,
1158 34 : &gorc->h_contract_terms,
1159 : &process_refunds_cb,
1160 : gorc);
1161 34 : if (0 > qs)
1162 : {
1163 0 : GNUNET_break (0);
1164 0 : phase_end (gorc,
1165 : TALER_MHD_reply_with_error (
1166 : gorc->sc.con,
1167 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1168 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1169 : "detailed refunds"));
1170 0 : return;
1171 : }
1172 34 : if (gorc->refund_currency_mismatch)
1173 : {
1174 0 : GNUNET_break (0);
1175 0 : phase_end (gorc,
1176 : TALER_MHD_reply_with_error (
1177 : gorc->sc.con,
1178 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1179 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1180 : "refunds in different currency than original order price"));
1181 0 : return;
1182 : }
1183 34 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1184 : "Total refunds are %s\n",
1185 : TALER_amount2s (&gorc->refund_amount));
1186 34 : gorc->phase++;
1187 : }
1188 :
1189 :
1190 : /**
1191 : * Function called with each @a coin_pub that was deposited into the
1192 : * @a h_wire account of the merchant for the @a deposit_serial as part
1193 : * of the payment for the order identified by @a cls.
1194 : *
1195 : * Queries the exchange for the payment status associated with the
1196 : * given coin.
1197 : *
1198 : * @param cls a `struct GetOrderRequestContext`
1199 : * @param deposit_serial identifies the deposit operation
1200 : * @param exchange_url URL of the exchange that issued @a coin_pub
1201 : * @param h_wire hash of the merchant's wire account into which the deposit was made
1202 : * @param deposit_timestamp when was the deposit made
1203 : * @param amount_with_fee amount the exchange will deposit for this coin
1204 : * @param deposit_fee fee the exchange will charge for this coin
1205 : * @param coin_pub public key of the deposited coin
1206 : */
1207 : static void
1208 34 : deposit_cb (
1209 : void *cls,
1210 : uint64_t deposit_serial,
1211 : const char *exchange_url,
1212 : const struct TALER_MerchantWireHashP *h_wire,
1213 : struct GNUNET_TIME_Timestamp deposit_timestamp,
1214 : const struct TALER_Amount *amount_with_fee,
1215 : const struct TALER_Amount *deposit_fee,
1216 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
1217 : {
1218 34 : struct GetOrderRequestContext *gorc = cls;
1219 : struct TransferQuery *tq;
1220 :
1221 34 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1222 : "Checking deposit status for coin %s (over %s)\n",
1223 : TALER_B2S (coin_pub),
1224 : TALER_amount2s (amount_with_fee));
1225 : gorc->last_payment
1226 34 : = GNUNET_TIME_timestamp_max (gorc->last_payment,
1227 : deposit_timestamp);
1228 34 : tq = GNUNET_new (struct TransferQuery);
1229 34 : tq->gorc = gorc;
1230 34 : tq->exchange_url = GNUNET_strdup (exchange_url);
1231 34 : tq->deposit_serial = deposit_serial;
1232 34 : GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
1233 : gorc->tq_tail,
1234 : tq);
1235 34 : tq->coin_pub = *coin_pub;
1236 34 : tq->h_wire = *h_wire;
1237 34 : tq->amount_with_fee = *amount_with_fee;
1238 34 : tq->deposit_fee = *deposit_fee;
1239 34 : }
1240 :
1241 :
1242 : /**
1243 : * Check wire transfer status for the order at the exchange.
1244 : *
1245 : * @param[in,out] gorc order context to update
1246 : */
1247 : static void
1248 34 : phase_check_deposits (struct GetOrderRequestContext *gorc)
1249 : {
1250 34 : GNUNET_assert (! gorc->order_only);
1251 34 : GNUNET_assert (gorc->paid);
1252 :
1253 : /* amount must be always valid for paid orders */
1254 34 : GNUNET_assert (GNUNET_OK ==
1255 : TALER_amount_is_valid (&gorc->contract_amount));
1256 :
1257 34 : GNUNET_assert (GNUNET_OK ==
1258 : TALER_amount_set_zero (gorc->contract_amount.currency,
1259 : &gorc->deposits_total));
1260 34 : GNUNET_assert (GNUNET_OK ==
1261 : TALER_amount_set_zero (gorc->contract_amount.currency,
1262 : &gorc->deposit_fees_total));
1263 34 : TMH_db->lookup_deposits_by_order (TMH_db->cls,
1264 : gorc->order_serial,
1265 : &deposit_cb,
1266 : gorc);
1267 34 : gorc->phase++;
1268 34 : }
1269 :
1270 :
1271 : /**
1272 : * Function called with available wire details, to be added to
1273 : * the response.
1274 : *
1275 : * @param cls a `struct GetOrderRequestContext`
1276 : * @param wtid wire transfer subject of the wire transfer for the coin
1277 : * @param exchange_url base URL of the exchange that made the payment
1278 : * @param execution_time when was the payment made
1279 : * @param deposit_value contribution of the coin to the total wire transfer value
1280 : * @param deposit_fee deposit fee charged by the exchange for the coin
1281 : * @param transfer_confirmed did the merchant confirm that a wire transfer with
1282 : * @a wtid over the total amount happened?
1283 : */
1284 : static void
1285 13 : process_transfer_details (
1286 : void *cls,
1287 : const struct TALER_WireTransferIdentifierRawP *wtid,
1288 : const char *exchange_url,
1289 : struct GNUNET_TIME_Timestamp execution_time,
1290 : const struct TALER_Amount *deposit_value,
1291 : const struct TALER_Amount *deposit_fee,
1292 : bool transfer_confirmed)
1293 : {
1294 13 : struct GetOrderRequestContext *gorc = cls;
1295 13 : json_t *wire_details = gorc->wire_details;
1296 : struct TALER_Amount wired;
1297 :
1298 13 : if ( (GNUNET_OK !=
1299 13 : TALER_amount_cmp_currency (&gorc->deposits_total,
1300 13 : deposit_value)) ||
1301 : (GNUNET_OK !=
1302 13 : TALER_amount_cmp_currency (&gorc->deposit_fees_total,
1303 : deposit_fee)) )
1304 : {
1305 0 : GNUNET_break (0);
1306 0 : gorc->deposit_currency_mismatch = true;
1307 0 : return;
1308 : }
1309 :
1310 : /* Compute total amount *wired* */
1311 13 : GNUNET_assert (0 <=
1312 : TALER_amount_add (&gorc->deposits_total,
1313 : &gorc->deposits_total,
1314 : deposit_value));
1315 13 : GNUNET_assert (0 <=
1316 : TALER_amount_add (&gorc->deposit_fees_total,
1317 : &gorc->deposit_fees_total,
1318 : deposit_fee));
1319 13 : GNUNET_assert (0 <= TALER_amount_subtract (&wired,
1320 : deposit_value,
1321 : deposit_fee));
1322 13 : GNUNET_assert (0 ==
1323 : json_array_append_new (
1324 : wire_details,
1325 : GNUNET_JSON_PACK (
1326 : GNUNET_JSON_pack_data_auto ("wtid",
1327 : wtid),
1328 : GNUNET_JSON_pack_string ("exchange_url",
1329 : exchange_url),
1330 : TALER_JSON_pack_amount ("amount",
1331 : &wired),
1332 : GNUNET_JSON_pack_timestamp ("execution_time",
1333 : execution_time),
1334 : GNUNET_JSON_pack_bool ("confirmed",
1335 : transfer_confirmed))));
1336 : }
1337 :
1338 :
1339 : /**
1340 : * Check transfer status in local database.
1341 : *
1342 : * @param[in,out] gorc order context to update
1343 : */
1344 : static void
1345 34 : phase_check_local_transfers (struct GetOrderRequestContext *gorc)
1346 : {
1347 34 : struct TMH_HandlerContext *hc = gorc->hc;
1348 : enum GNUNET_DB_QueryStatus qs;
1349 :
1350 34 : GNUNET_assert (gorc->paid);
1351 34 : GNUNET_assert (! gorc->order_only);
1352 :
1353 34 : GNUNET_assert (GNUNET_OK ==
1354 : TALER_amount_set_zero (gorc->contract_amount.currency,
1355 : &gorc->deposits_total));
1356 34 : GNUNET_assert (GNUNET_OK ==
1357 : TALER_amount_set_zero (gorc->contract_amount.currency,
1358 : &gorc->deposit_fees_total));
1359 :
1360 34 : qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
1361 : gorc->order_serial,
1362 : &process_transfer_details,
1363 : gorc);
1364 34 : if (0 > qs)
1365 : {
1366 0 : GNUNET_break (0);
1367 0 : phase_end (gorc,
1368 : TALER_MHD_reply_with_error (gorc->sc.con,
1369 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1370 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1371 : "transfer details"));
1372 0 : return;
1373 : }
1374 34 : if (gorc->deposit_currency_mismatch)
1375 : {
1376 0 : GNUNET_break (0);
1377 0 : phase_end (gorc,
1378 : TALER_MHD_reply_with_error (gorc->sc.con,
1379 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1380 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1381 : "deposits in different currency than original order price"));
1382 0 : return;
1383 : }
1384 :
1385 34 : if (! gorc->wired)
1386 : {
1387 : /* we believe(d) the wire transfer did not happen yet, check if maybe
1388 : in light of new evidence it did */
1389 : struct TALER_Amount expect_total;
1390 :
1391 28 : if (0 >
1392 28 : TALER_amount_subtract (&expect_total,
1393 28 : &gorc->contract_amount,
1394 28 : &gorc->refund_amount))
1395 : {
1396 0 : GNUNET_break (0);
1397 0 : phase_end (gorc,
1398 : TALER_MHD_reply_with_error (
1399 : gorc->sc.con,
1400 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1401 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1402 : "refund exceeds contract value"));
1403 0 : return;
1404 : }
1405 28 : if (0 >
1406 28 : TALER_amount_subtract (&expect_total,
1407 : &expect_total,
1408 28 : &gorc->deposit_fees_total))
1409 : {
1410 0 : GNUNET_break (0);
1411 0 : phase_end (gorc,
1412 : TALER_MHD_reply_with_error (
1413 : gorc->sc.con,
1414 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1415 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1416 : "deposit fees exceed total minus refunds"));
1417 0 : return;
1418 : }
1419 28 : if (0 >=
1420 28 : TALER_amount_cmp (&expect_total,
1421 28 : &gorc->deposits_total))
1422 : {
1423 : /* expect_total <= gorc->deposits_total: good: we got the wire transfer */
1424 7 : gorc->wired = true;
1425 7 : qs = TMH_db->mark_order_wired (TMH_db->cls,
1426 : gorc->order_serial);
1427 7 : GNUNET_break (qs >= 0); /* just warn if transaction failed */
1428 7 : TMH_notify_order_change (hc->instance,
1429 : TMH_OSF_PAID
1430 : | TMH_OSF_WIRED,
1431 7 : gorc->contract_terms->timestamp,
1432 : gorc->order_serial);
1433 : }
1434 : }
1435 34 : gorc->phase++;
1436 : }
1437 :
1438 :
1439 : /**
1440 : * Generate final result for the status request.
1441 : *
1442 : * @param[in,out] gorc order context to update
1443 : */
1444 : static void
1445 34 : phase_reply_result (struct GetOrderRequestContext *gorc)
1446 : {
1447 34 : struct TMH_HandlerContext *hc = gorc->hc;
1448 : MHD_RESULT ret;
1449 : char *order_status_url;
1450 :
1451 34 : GNUNET_assert (gorc->paid);
1452 34 : GNUNET_assert (! gorc->order_only);
1453 :
1454 : {
1455 34 : struct TALER_PrivateContractHashP *h_contract = NULL;
1456 :
1457 : /* In a session-bound payment, allow the browser to check the order
1458 : * status page (e.g. to get a refund).
1459 : *
1460 : * Note that we don't allow this outside of session-based payment, as
1461 : * otherwise this becomes an oracle to convert order_id to h_contract.
1462 : */
1463 34 : if (NULL != gorc->session_id)
1464 2 : h_contract = &gorc->h_contract_terms;
1465 :
1466 : order_status_url =
1467 34 : TMH_make_order_status_url (gorc->sc.con,
1468 34 : hc->infix,
1469 : gorc->session_id,
1470 34 : hc->instance->settings.id,
1471 : &gorc->claim_token,
1472 : h_contract);
1473 : }
1474 34 : if (GNUNET_TIME_absolute_is_zero (gorc->last_payment.abs_time))
1475 : {
1476 0 : GNUNET_break (GNUNET_YES ==
1477 : TALER_amount_is_zero (&gorc->contract_amount));
1478 0 : gorc->last_payment = gorc->contract_terms->timestamp;
1479 : }
1480 34 : ret = TALER_MHD_REPLY_JSON_PACK (
1481 : gorc->sc.con,
1482 : MHD_HTTP_OK,
1483 : // Deprecated in protocol v6.
1484 : GNUNET_JSON_pack_array_steal ("wire_reports",
1485 : json_array ()),
1486 : GNUNET_JSON_pack_uint64 ("exchange_code",
1487 : gorc->exchange_ec),
1488 : GNUNET_JSON_pack_uint64 ("exchange_http_status",
1489 : gorc->exchange_hc),
1490 : /* legacy: */
1491 : GNUNET_JSON_pack_uint64 ("exchange_ec",
1492 : gorc->exchange_ec),
1493 : /* legacy: */
1494 : GNUNET_JSON_pack_uint64 ("exchange_hc",
1495 : gorc->exchange_hc),
1496 : TALER_JSON_pack_amount ("deposit_total",
1497 : &gorc->deposits_total),
1498 : GNUNET_JSON_pack_object_incref ("contract_terms",
1499 : gorc->contract_terms_json),
1500 : GNUNET_JSON_pack_string ("order_status",
1501 : "paid"),
1502 : GNUNET_JSON_pack_timestamp ("last_payment",
1503 : gorc->last_payment),
1504 : GNUNET_JSON_pack_bool ("refunded",
1505 : gorc->refunded),
1506 : GNUNET_JSON_pack_bool ("wired",
1507 : gorc->wired),
1508 : GNUNET_JSON_pack_bool ("refund_pending",
1509 : gorc->refund_pending),
1510 : GNUNET_JSON_pack_allow_null (
1511 : TALER_JSON_pack_amount ("refund_amount",
1512 : &gorc->refund_amount)),
1513 : GNUNET_JSON_pack_array_steal ("wire_details",
1514 : gorc->wire_details),
1515 : GNUNET_JSON_pack_array_steal ("refund_details",
1516 : gorc->refund_details),
1517 : GNUNET_JSON_pack_string ("order_status_url",
1518 : order_status_url),
1519 : (gorc->choice_index >= 0)
1520 : ? GNUNET_JSON_pack_int64 ("choice_index",
1521 : gorc->choice_index)
1522 : : GNUNET_JSON_pack_end_ ());
1523 34 : GNUNET_free (order_status_url);
1524 34 : gorc->wire_details = NULL;
1525 34 : gorc->refund_details = NULL;
1526 34 : phase_end (gorc,
1527 : ret);
1528 34 : }
1529 :
1530 :
1531 : /**
1532 : * End with error status in wire_hc and wire_ec.
1533 : *
1534 : * @param[in,out] gorc order context to update
1535 : */
1536 : static void
1537 0 : phase_error (struct GetOrderRequestContext *gorc)
1538 : {
1539 0 : GNUNET_assert (TALER_EC_NONE != gorc->wire_ec);
1540 0 : phase_end (gorc,
1541 : TALER_MHD_reply_with_error (gorc->sc.con,
1542 : gorc->wire_hc,
1543 : gorc->wire_ec,
1544 : NULL));
1545 0 : }
1546 :
1547 :
1548 : MHD_RESULT
1549 61 : TMH_private_get_orders_ID (
1550 : const struct TMH_RequestHandler *rh,
1551 : struct MHD_Connection *connection,
1552 : struct TMH_HandlerContext *hc)
1553 : {
1554 61 : struct GetOrderRequestContext *gorc = hc->ctx;
1555 :
1556 61 : if (NULL == gorc)
1557 : {
1558 : /* First time here, parse request and check order is known */
1559 59 : GNUNET_assert (NULL != hc->infix);
1560 59 : gorc = GNUNET_new (struct GetOrderRequestContext);
1561 59 : hc->cc = &gorc_cleanup;
1562 59 : hc->ctx = gorc;
1563 59 : gorc->sc.con = connection;
1564 59 : gorc->hc = hc;
1565 59 : gorc->wire_details = json_array ();
1566 59 : GNUNET_assert (NULL != gorc->wire_details);
1567 59 : gorc->refund_details = json_array ();
1568 59 : GNUNET_assert (NULL != gorc->refund_details);
1569 59 : gorc->session_id = MHD_lookup_connection_value (connection,
1570 : MHD_GET_ARGUMENT_KIND,
1571 : "session_id");
1572 59 : if (! (TALER_MHD_arg_to_yna (connection,
1573 : "allow_refunded_for_repurchase",
1574 : TALER_EXCHANGE_YNA_NO,
1575 : &gorc->allow_refunded_for_repurchase)) )
1576 0 : return TALER_MHD_reply_with_error (connection,
1577 : MHD_HTTP_BAD_REQUEST,
1578 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1579 : "allow_refunded_for_repurchase");
1580 59 : TALER_MHD_parse_request_timeout (connection,
1581 : &gorc->sc.long_poll_timeout);
1582 59 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1583 : "Starting GET /private/orders/%s processing with timeout %s\n",
1584 : hc->infix,
1585 : GNUNET_STRINGS_absolute_time_to_string (
1586 : gorc->sc.long_poll_timeout));
1587 : }
1588 61 : if (GNUNET_SYSERR == gorc->suspended)
1589 0 : return MHD_NO; /* we are in shutdown */
1590 : while (1)
1591 : {
1592 1053 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1593 : "Processing order %s in phase %d\n",
1594 : hc->infix,
1595 : (int) gorc->phase);
1596 557 : switch (gorc->phase)
1597 : {
1598 59 : case GOP_INIT:
1599 59 : phase_init (gorc);
1600 59 : break;
1601 61 : case GOP_FETCH_CONTRACT:
1602 61 : phase_fetch_contract (gorc);
1603 61 : break;
1604 61 : case GOP_PARSE_CONTRACT:
1605 61 : phase_parse_contract (gorc);
1606 61 : break;
1607 61 : case GOP_CHECK_PAID:
1608 61 : phase_check_paid (gorc);
1609 61 : break;
1610 61 : case GOP_CHECK_REPURCHASE:
1611 61 : phase_check_repurchase (gorc);
1612 61 : break;
1613 57 : case GOP_UNPAID_FINISH:
1614 57 : phase_unpaid_finish (gorc);
1615 57 : break;
1616 34 : case GOP_CHECK_REFUNDS:
1617 34 : phase_check_refunds (gorc);
1618 34 : break;
1619 34 : case GOP_CHECK_DEPOSITS:
1620 34 : phase_check_deposits (gorc);
1621 34 : break;
1622 34 : case GOP_CHECK_LOCAL_TRANSFERS:
1623 34 : phase_check_local_transfers (gorc);
1624 34 : break;
1625 34 : case GOP_REPLY_RESULT:
1626 34 : phase_reply_result (gorc);
1627 34 : break;
1628 0 : case GOP_ERROR:
1629 0 : phase_error (gorc);
1630 0 : break;
1631 2 : case GOP_SUSPENDED_ON_UNPAID:
1632 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1633 : "Suspending order request awaiting payment\n");
1634 2 : return MHD_YES;
1635 59 : case GOP_END_YES:
1636 59 : return MHD_YES;
1637 0 : case GOP_END_NO:
1638 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1639 : "Closing connection, no response generated\n");
1640 0 : return MHD_NO;
1641 : }
1642 : } /* end first-time per-request initialization */
1643 : }
|