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 15 : TMH_force_gorc_resume (void)
421 : {
422 : struct GetOrderRequestContext *gorc;
423 :
424 15 : 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 15 : }
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 64 : gorc_cleanup (void *cls)
475 : {
476 64 : struct GetOrderRequestContext *gorc = cls;
477 : struct TransferQuery *tq;
478 :
479 102 : while (NULL != (tq = gorc->tq_head))
480 : {
481 38 : GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
482 : gorc->tq_tail,
483 : tq);
484 38 : GNUNET_free (tq->exchange_url);
485 38 : GNUNET_free (tq);
486 : }
487 :
488 64 : if (NULL != gorc->contract_terms_json)
489 64 : json_decref (gorc->contract_terms_json);
490 64 : if (NULL != gorc->contract_terms)
491 : {
492 64 : TALER_MERCHANT_contract_free (gorc->contract_terms);
493 64 : gorc->contract_terms = NULL;
494 : }
495 64 : if (NULL != gorc->wire_details)
496 26 : json_decref (gorc->wire_details);
497 64 : if (NULL != gorc->refund_details)
498 26 : json_decref (gorc->refund_details);
499 64 : if (NULL != gorc->tt)
500 : {
501 0 : GNUNET_SCHEDULER_cancel (gorc->tt);
502 0 : gorc->tt = NULL;
503 : }
504 64 : if (NULL != gorc->eh)
505 : {
506 4 : TMH_db->event_listen_cancel (gorc->eh);
507 4 : gorc->eh = NULL;
508 : }
509 64 : if (NULL != gorc->session_eh)
510 : {
511 0 : TMH_db->event_listen_cancel (gorc->session_eh);
512 0 : gorc->session_eh = NULL;
513 : }
514 64 : GNUNET_free (gorc);
515 64 : }
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 64 : phase_end (struct GetOrderRequestContext *gorc,
527 : MHD_RESULT mret)
528 : {
529 64 : gorc->phase = (MHD_YES == mret)
530 : ? GOP_END_YES
531 64 : : GOP_END_NO;
532 64 : }
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 64 : phase_init (struct GetOrderRequestContext *gorc)
542 : {
543 64 : struct TMH_HandlerContext *hc = gorc->hc;
544 64 : struct TMH_OrderPayEventP pay_eh = {
545 64 : .header.size = htons (sizeof (pay_eh)),
546 64 : .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
547 64 : .merchant_pub = hc->instance->merchant_pub
548 : };
549 :
550 64 : if (! GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
551 : {
552 60 : gorc->phase++;
553 60 : 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 66 : phase_fetch_contract (struct GetOrderRequestContext *gorc)
605 : {
606 66 : struct TMH_HandlerContext *hc = gorc->hc;
607 : enum GNUNET_DB_QueryStatus qs;
608 :
609 66 : 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 66 : TMH_db->preflight (TMH_db->cls);
623 66 : qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
624 66 : hc->instance->settings.id,
625 66 : 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 66 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
635 : "lookup_contract_terms (%s) returned %d\n",
636 : hc->infix,
637 : (int) qs);
638 66 : 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 66 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
653 : {
654 54 : 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 54 : gorc->phase++;
660 54 : 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 66 : phase_parse_contract (struct GetOrderRequestContext *gorc)
713 : {
714 66 : struct TMH_HandlerContext *hc = gorc->hc;
715 :
716 66 : if (NULL == gorc->contract_terms)
717 : {
718 64 : gorc->contract_terms = TALER_MERCHANT_contract_parse (
719 : gorc->contract_terms_json,
720 : true);
721 :
722 64 : 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 66 : switch (gorc->contract_terms->version)
736 : {
737 66 : case TALER_MERCHANT_CONTRACT_VERSION_0:
738 66 : gorc->contract_amount = gorc->contract_terms->details.v0.brutto;
739 66 : 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,
750 : MHD_HTTP_INTERNAL_SERVER_ERROR,
751 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
752 : NULL));
753 0 : return;
754 : }
755 :
756 0 : gorc->contract_amount =
757 0 : gorc->contract_terms->details.v1.choices[gorc->choice_index].amount;
758 : }
759 : else
760 : {
761 0 : GNUNET_break (gorc->order_only);
762 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
763 : "Choice index %i for order %s is invalid or not yet available",
764 : gorc->choice_index,
765 : gorc->contract_terms->order_id);
766 : }
767 0 : break;
768 0 : default:
769 : {
770 0 : GNUNET_break (0);
771 0 : phase_end (gorc,
772 : TALER_MHD_reply_with_error (
773 : gorc->sc.con,
774 : MHD_HTTP_INTERNAL_SERVER_ERROR,
775 : TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
776 : NULL));
777 0 : return;
778 : }
779 : }
780 :
781 120 : if ( (! gorc->order_only) &&
782 : (GNUNET_OK !=
783 54 : TALER_JSON_contract_hash (gorc->contract_terms_json,
784 : &gorc->h_contract_terms)) )
785 : {
786 0 : GNUNET_break (0);
787 0 : phase_end (gorc,
788 : TALER_MHD_reply_with_error (gorc->sc.con,
789 : MHD_HTTP_INTERNAL_SERVER_ERROR,
790 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
791 : NULL));
792 0 : return;
793 : }
794 66 : GNUNET_assert (NULL != gorc->contract_terms_json);
795 66 : GNUNET_assert (NULL != gorc->contract_terms);
796 66 : gorc->phase++;
797 : }
798 :
799 :
800 : /**
801 : * Check payment status of the order.
802 : *
803 : * @param[in,out] gorc order context to update
804 : */
805 : static void
806 66 : phase_check_paid (struct GetOrderRequestContext *gorc)
807 : {
808 66 : struct TMH_HandlerContext *hc = gorc->hc;
809 :
810 66 : if (gorc->order_only)
811 : {
812 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
813 : "Order %s unclaimed, no need to lookup payment status\n",
814 : hc->infix);
815 12 : GNUNET_assert (! gorc->paid);
816 12 : GNUNET_assert (! gorc->wired);
817 12 : gorc->phase++;
818 12 : return;
819 : }
820 54 : if (NULL == gorc->session_id)
821 : {
822 44 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
823 : "No session ID, do not need to lookup session-ID specific payment status (%s/%s)\n",
824 : gorc->paid ? "paid" : "unpaid",
825 : gorc->wired ? "wired" : "unwired");
826 44 : gorc->phase++;
827 44 : return;
828 : }
829 10 : if (! gorc->paid_session_matches)
830 : {
831 8 : gorc->paid = false;
832 8 : gorc->wired = false;
833 : }
834 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
835 : "Order %s %s for session %s (%s)\n",
836 : hc->infix,
837 : gorc->paid ? "paid" : "unpaid",
838 : gorc->session_id,
839 : gorc->wired ? "wired" : "unwired");
840 10 : gorc->phase++;
841 : }
842 :
843 :
844 : /**
845 : * Check if re-purchase detection applies to the order.
846 : *
847 : * @param[in,out] gorc order context to update
848 : */
849 : static void
850 66 : phase_check_repurchase (struct GetOrderRequestContext *gorc)
851 : {
852 66 : struct TMH_HandlerContext *hc = gorc->hc;
853 66 : char *already_paid_order_id = NULL;
854 : enum GNUNET_DB_QueryStatus qs;
855 : char *taler_pay_uri;
856 : char *order_status_url;
857 : MHD_RESULT ret;
858 :
859 66 : if ( (gorc->paid) ||
860 28 : (NULL == gorc->contract_terms->fulfillment_url) ||
861 17 : (NULL == gorc->session_id) )
862 : {
863 : /* Repurchase cannot apply */
864 58 : gorc->phase++;
865 62 : return;
866 : }
867 8 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
868 : "Running re-purchase detection for %s/%s\n",
869 : gorc->session_id,
870 : gorc->contract_terms->fulfillment_url);
871 8 : qs = TMH_db->lookup_order_by_fulfillment (
872 8 : TMH_db->cls,
873 8 : hc->instance->settings.id,
874 8 : gorc->contract_terms->fulfillment_url,
875 : gorc->session_id,
876 : TALER_EXCHANGE_YNA_NO !=
877 8 : gorc->allow_refunded_for_repurchase,
878 : &already_paid_order_id);
879 8 : if (0 > qs)
880 : {
881 : /* single, read-only SQL statements should never cause
882 : serialization problems, and the entry should exist as per above */
883 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
884 0 : phase_end (gorc,
885 : TALER_MHD_reply_with_error (gorc->sc.con,
886 : MHD_HTTP_INTERNAL_SERVER_ERROR,
887 : TALER_EC_GENERIC_DB_FETCH_FAILED,
888 : "order by fulfillment"));
889 0 : return;
890 : }
891 8 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
892 : {
893 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
894 : "No already paid order for %s/%s\n",
895 : gorc->session_id,
896 : gorc->contract_terms->fulfillment_url);
897 4 : gorc->phase++;
898 4 : return;
899 : }
900 :
901 : /* User did pay for this order, but under a different session; ask wallet to
902 : switch order ID */
903 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
904 : "Found already paid order %s\n",
905 : already_paid_order_id);
906 4 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
907 4 : hc->infix,
908 : gorc->session_id,
909 4 : hc->instance->settings.id,
910 : &gorc->claim_token);
911 4 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
912 4 : hc->infix,
913 : gorc->session_id,
914 4 : hc->instance->settings.id,
915 : &gorc->claim_token,
916 : NULL);
917 4 : if ( (NULL == taler_pay_uri) ||
918 : (NULL == order_status_url) )
919 : {
920 0 : GNUNET_break_op (0);
921 0 : GNUNET_free (taler_pay_uri);
922 0 : GNUNET_free (order_status_url);
923 0 : phase_end (gorc,
924 : TALER_MHD_reply_with_error (gorc->sc.con,
925 : MHD_HTTP_BAD_REQUEST,
926 : TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
927 : "host"));
928 0 : return;
929 : }
930 4 : ret = TALER_MHD_REPLY_JSON_PACK (
931 : gorc->sc.con,
932 : MHD_HTTP_OK,
933 : GNUNET_JSON_pack_string ("taler_pay_uri",
934 : taler_pay_uri),
935 : GNUNET_JSON_pack_string ("order_status_url",
936 : order_status_url),
937 : GNUNET_JSON_pack_string ("order_status",
938 : "unpaid"),
939 : GNUNET_JSON_pack_string ("already_paid_order_id",
940 : already_paid_order_id),
941 : GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
942 : gorc->contract_terms->fulfillment_url),
943 : /* undefined for unpaid v1 contracts */
944 : GNUNET_JSON_pack_allow_null (
945 : TALER_JSON_pack_amount ("total_amount",
946 : TALER_amount_is_valid (&gorc->contract_amount)
947 : ? &gorc->contract_amount
948 : : NULL)),
949 : GNUNET_JSON_pack_string ("summary",
950 : gorc->contract_terms->summary),
951 : GNUNET_JSON_pack_timestamp ("pay_deadline",
952 : gorc->contract_terms->pay_deadline),
953 : GNUNET_JSON_pack_timestamp ("creation_time",
954 : gorc->contract_terms->timestamp));
955 4 : GNUNET_free (order_status_url);
956 4 : GNUNET_free (taler_pay_uri);
957 4 : GNUNET_free (already_paid_order_id);
958 4 : phase_end (gorc,
959 : ret);
960 : }
961 :
962 :
963 : /**
964 : * Check if we should suspend until the order is paid.
965 : *
966 : * @param[in,out] gorc order context to update
967 : */
968 : static void
969 62 : phase_unpaid_finish (struct GetOrderRequestContext *gorc)
970 : {
971 62 : struct TMH_HandlerContext *hc = gorc->hc;
972 : char *taler_pay_uri;
973 : char *order_status_url;
974 : MHD_RESULT ret;
975 :
976 62 : if (gorc->paid)
977 : {
978 38 : gorc->phase++;
979 50 : return;
980 : }
981 : /* User never paid for this order, suspend waiting
982 : on payment or return details. */
983 24 : if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
984 : {
985 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
986 : "Suspending GET /private/orders/%s\n",
987 : hc->infix);
988 2 : GNUNET_CONTAINER_DLL_insert (gorc_head,
989 : gorc_tail,
990 : gorc);
991 2 : gorc->phase = GOP_SUSPENDED_ON_UNPAID;
992 2 : gorc->suspended = GNUNET_YES;
993 2 : MHD_suspend_connection (gorc->sc.con);
994 2 : return;
995 : }
996 22 : order_status_url = TMH_make_order_status_url (gorc->sc.con,
997 22 : hc->infix,
998 : gorc->session_id,
999 22 : hc->instance->settings.id,
1000 : &gorc->claim_token,
1001 : NULL);
1002 22 : if (! gorc->order_only)
1003 : {
1004 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1005 : "Order %s claimed but not paid yet\n",
1006 : hc->infix);
1007 10 : phase_end (gorc,
1008 10 : TALER_MHD_REPLY_JSON_PACK (
1009 : gorc->sc.con,
1010 : MHD_HTTP_OK,
1011 : GNUNET_JSON_pack_string ("order_status_url",
1012 : order_status_url),
1013 : GNUNET_JSON_pack_object_incref ("contract_terms",
1014 : gorc->contract_terms_json),
1015 : GNUNET_JSON_pack_string ("order_status",
1016 : "claimed")));
1017 10 : GNUNET_free (order_status_url);
1018 10 : return;
1019 : }
1020 12 : taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
1021 12 : hc->infix,
1022 : gorc->session_id,
1023 12 : hc->instance->settings.id,
1024 : &gorc->claim_token);
1025 12 : ret = TALER_MHD_REPLY_JSON_PACK (
1026 : gorc->sc.con,
1027 : MHD_HTTP_OK,
1028 : GNUNET_JSON_pack_string ("taler_pay_uri",
1029 : taler_pay_uri),
1030 : GNUNET_JSON_pack_string ("order_status_url",
1031 : order_status_url),
1032 : GNUNET_JSON_pack_string ("order_status",
1033 : "unpaid"),
1034 : /* undefined for unpaid v1 contracts */
1035 : GNUNET_JSON_pack_allow_null (
1036 : TALER_JSON_pack_amount ("total_amount",
1037 : &gorc->contract_amount)),
1038 : GNUNET_JSON_pack_string ("summary",
1039 : gorc->contract_terms->summary),
1040 : GNUNET_JSON_pack_timestamp ("creation_time",
1041 : gorc->contract_terms->timestamp));
1042 12 : GNUNET_free (taler_pay_uri);
1043 12 : GNUNET_free (order_status_url);
1044 12 : phase_end (gorc,
1045 : ret);
1046 :
1047 : }
1048 :
1049 :
1050 : /**
1051 : * Function called with information about a refund.
1052 : * It is responsible for summing up the refund amount.
1053 : *
1054 : * @param cls closure
1055 : * @param refund_serial unique serial number of the refund
1056 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
1057 : * @param coin_pub public coin from which the refund comes from
1058 : * @param exchange_url URL of the exchange that issued @a coin_pub
1059 : * @param rtransaction_id identificator of the refund
1060 : * @param reason human-readable explanation of the refund
1061 : * @param refund_amount refund amount which is being taken from @a coin_pub
1062 : * @param pending true if the this refund was not yet processed by the wallet/exchange
1063 : */
1064 : static void
1065 8 : process_refunds_cb (
1066 : void *cls,
1067 : uint64_t refund_serial,
1068 : struct GNUNET_TIME_Timestamp timestamp,
1069 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
1070 : const char *exchange_url,
1071 : uint64_t rtransaction_id,
1072 : const char *reason,
1073 : const struct TALER_Amount *refund_amount,
1074 : bool pending)
1075 : {
1076 8 : struct GetOrderRequestContext *gorc = cls;
1077 :
1078 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1079 : "Found refund %llu over %s for reason %s\n",
1080 : (unsigned long long) rtransaction_id,
1081 : TALER_amount2s (refund_amount),
1082 : reason);
1083 8 : GNUNET_assert (
1084 : 0 ==
1085 : json_array_append_new (
1086 : gorc->refund_details,
1087 : GNUNET_JSON_PACK (
1088 : TALER_JSON_pack_amount ("amount",
1089 : refund_amount),
1090 : GNUNET_JSON_pack_bool ("pending",
1091 : pending),
1092 : GNUNET_JSON_pack_timestamp ("timestamp",
1093 : timestamp),
1094 : GNUNET_JSON_pack_string ("reason",
1095 : reason))));
1096 : /* For refunded coins, we are not charged deposit fees, so subtract those
1097 : again */
1098 8 : for (struct TransferQuery *tq = gorc->tq_head;
1099 8 : NULL != tq;
1100 0 : tq = tq->next)
1101 : {
1102 0 : if (0 !=
1103 0 : strcmp (exchange_url,
1104 0 : tq->exchange_url))
1105 0 : continue;
1106 0 : if (0 !=
1107 0 : GNUNET_memcmp (&tq->coin_pub,
1108 : coin_pub))
1109 0 : continue;
1110 0 : if (GNUNET_OK !=
1111 0 : TALER_amount_cmp_currency (
1112 0 : &gorc->deposit_fees_total,
1113 0 : &tq->deposit_fee))
1114 : {
1115 0 : gorc->refund_currency_mismatch = true;
1116 0 : return;
1117 : }
1118 0 : GNUNET_assert (
1119 : 0 <=
1120 : TALER_amount_subtract (&gorc->deposit_fees_total,
1121 : &gorc->deposit_fees_total,
1122 : &tq->deposit_fee));
1123 : }
1124 8 : if (GNUNET_OK !=
1125 8 : TALER_amount_cmp_currency (
1126 8 : &gorc->refund_amount,
1127 : refund_amount))
1128 : {
1129 0 : gorc->refund_currency_mismatch = true;
1130 0 : return;
1131 : }
1132 8 : GNUNET_assert (0 <=
1133 : TALER_amount_add (&gorc->refund_amount,
1134 : &gorc->refund_amount,
1135 : refund_amount));
1136 8 : gorc->refunded = true;
1137 8 : gorc->refund_pending |= pending;
1138 : }
1139 :
1140 :
1141 : /**
1142 : * Check refund status for the order.
1143 : *
1144 : * @param[in,out] gorc order context to update
1145 : */
1146 : static void
1147 38 : phase_check_refunds (struct GetOrderRequestContext *gorc)
1148 : {
1149 38 : struct TMH_HandlerContext *hc = gorc->hc;
1150 : enum GNUNET_DB_QueryStatus qs;
1151 :
1152 38 : GNUNET_assert (! gorc->order_only);
1153 38 : GNUNET_assert (gorc->paid);
1154 :
1155 : /* Accumulate refunds, if any. */
1156 38 : GNUNET_assert (GNUNET_OK ==
1157 : TALER_amount_set_zero (gorc->contract_amount.currency,
1158 : &gorc->refund_amount));
1159 :
1160 38 : qs = TMH_db->lookup_refunds_detailed (
1161 38 : TMH_db->cls,
1162 38 : hc->instance->settings.id,
1163 38 : &gorc->h_contract_terms,
1164 : &process_refunds_cb,
1165 : gorc);
1166 38 : if (0 > qs)
1167 : {
1168 0 : GNUNET_break (0);
1169 0 : phase_end (gorc,
1170 : TALER_MHD_reply_with_error (
1171 : gorc->sc.con,
1172 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1173 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1174 : "detailed refunds"));
1175 0 : return;
1176 : }
1177 38 : if (gorc->refund_currency_mismatch)
1178 : {
1179 0 : GNUNET_break (0);
1180 0 : phase_end (gorc,
1181 : TALER_MHD_reply_with_error (
1182 : gorc->sc.con,
1183 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1184 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1185 : "refunds in different currency than original order price"));
1186 0 : return;
1187 : }
1188 38 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1189 : "Total refunds are %s\n",
1190 : TALER_amount2s (&gorc->refund_amount));
1191 38 : gorc->phase++;
1192 : }
1193 :
1194 :
1195 : /**
1196 : * Function called with each @a coin_pub that was deposited into the
1197 : * @a h_wire account of the merchant for the @a deposit_serial as part
1198 : * of the payment for the order identified by @a cls.
1199 : *
1200 : * Queries the exchange for the payment status associated with the
1201 : * given coin.
1202 : *
1203 : * @param cls a `struct GetOrderRequestContext`
1204 : * @param deposit_serial identifies the deposit operation
1205 : * @param exchange_url URL of the exchange that issued @a coin_pub
1206 : * @param h_wire hash of the merchant's wire account into which the deposit was made
1207 : * @param deposit_timestamp when was the deposit made
1208 : * @param amount_with_fee amount the exchange will deposit for this coin
1209 : * @param deposit_fee fee the exchange will charge for this coin
1210 : * @param coin_pub public key of the deposited coin
1211 : */
1212 : static void
1213 38 : deposit_cb (
1214 : void *cls,
1215 : uint64_t deposit_serial,
1216 : const char *exchange_url,
1217 : const struct TALER_MerchantWireHashP *h_wire,
1218 : struct GNUNET_TIME_Timestamp deposit_timestamp,
1219 : const struct TALER_Amount *amount_with_fee,
1220 : const struct TALER_Amount *deposit_fee,
1221 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
1222 : {
1223 38 : struct GetOrderRequestContext *gorc = cls;
1224 : struct TransferQuery *tq;
1225 :
1226 38 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1227 : "Checking deposit status for coin %s (over %s)\n",
1228 : TALER_B2S (coin_pub),
1229 : TALER_amount2s (amount_with_fee));
1230 : gorc->last_payment
1231 38 : = GNUNET_TIME_timestamp_max (gorc->last_payment,
1232 : deposit_timestamp);
1233 38 : tq = GNUNET_new (struct TransferQuery);
1234 38 : tq->gorc = gorc;
1235 38 : tq->exchange_url = GNUNET_strdup (exchange_url);
1236 38 : tq->deposit_serial = deposit_serial;
1237 38 : GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
1238 : gorc->tq_tail,
1239 : tq);
1240 38 : tq->coin_pub = *coin_pub;
1241 38 : tq->h_wire = *h_wire;
1242 38 : tq->amount_with_fee = *amount_with_fee;
1243 38 : tq->deposit_fee = *deposit_fee;
1244 38 : }
1245 :
1246 :
1247 : /**
1248 : * Check wire transfer status for the order at the exchange.
1249 : *
1250 : * @param[in,out] gorc order context to update
1251 : */
1252 : static void
1253 38 : phase_check_deposits (struct GetOrderRequestContext *gorc)
1254 : {
1255 38 : GNUNET_assert (! gorc->order_only);
1256 38 : GNUNET_assert (gorc->paid);
1257 :
1258 : /* amount must be always valid for paid orders */
1259 38 : GNUNET_assert (GNUNET_OK ==
1260 : TALER_amount_is_valid (&gorc->contract_amount));
1261 :
1262 38 : GNUNET_assert (GNUNET_OK ==
1263 : TALER_amount_set_zero (gorc->contract_amount.currency,
1264 : &gorc->deposits_total));
1265 38 : GNUNET_assert (GNUNET_OK ==
1266 : TALER_amount_set_zero (gorc->contract_amount.currency,
1267 : &gorc->deposit_fees_total));
1268 38 : TMH_db->lookup_deposits_by_order (TMH_db->cls,
1269 : gorc->order_serial,
1270 : &deposit_cb,
1271 : gorc);
1272 38 : gorc->phase++;
1273 38 : }
1274 :
1275 :
1276 : /**
1277 : * Function called with available wire details, to be added to
1278 : * the response.
1279 : *
1280 : * @param cls a `struct GetOrderRequestContext`
1281 : * @param wtid wire transfer subject of the wire transfer for the coin
1282 : * @param exchange_url base URL of the exchange that made the payment
1283 : * @param execution_time when was the payment made
1284 : * @param deposit_value contribution of the coin to the total wire transfer value
1285 : * @param deposit_fee deposit fee charged by the exchange for the coin
1286 : * @param transfer_confirmed did the merchant confirm that a wire transfer with
1287 : * @a wtid over the total amount happened?
1288 : */
1289 : static void
1290 14 : process_transfer_details (
1291 : void *cls,
1292 : const struct TALER_WireTransferIdentifierRawP *wtid,
1293 : const char *exchange_url,
1294 : struct GNUNET_TIME_Timestamp execution_time,
1295 : const struct TALER_Amount *deposit_value,
1296 : const struct TALER_Amount *deposit_fee,
1297 : bool transfer_confirmed)
1298 : {
1299 14 : struct GetOrderRequestContext *gorc = cls;
1300 14 : json_t *wire_details = gorc->wire_details;
1301 : struct TALER_Amount wired;
1302 :
1303 14 : if ( (GNUNET_OK !=
1304 14 : TALER_amount_cmp_currency (&gorc->deposits_total,
1305 14 : deposit_value)) ||
1306 : (GNUNET_OK !=
1307 14 : TALER_amount_cmp_currency (&gorc->deposit_fees_total,
1308 : deposit_fee)) )
1309 : {
1310 0 : GNUNET_break (0);
1311 0 : gorc->deposit_currency_mismatch = true;
1312 0 : return;
1313 : }
1314 :
1315 : /* Compute total amount *wired* */
1316 14 : GNUNET_assert (0 <=
1317 : TALER_amount_add (&gorc->deposits_total,
1318 : &gorc->deposits_total,
1319 : deposit_value));
1320 14 : GNUNET_assert (0 <=
1321 : TALER_amount_add (&gorc->deposit_fees_total,
1322 : &gorc->deposit_fees_total,
1323 : deposit_fee));
1324 14 : GNUNET_assert (0 <= TALER_amount_subtract (&wired,
1325 : deposit_value,
1326 : deposit_fee));
1327 14 : GNUNET_assert (0 ==
1328 : json_array_append_new (
1329 : wire_details,
1330 : GNUNET_JSON_PACK (
1331 : GNUNET_JSON_pack_data_auto ("wtid",
1332 : wtid),
1333 : GNUNET_JSON_pack_string ("exchange_url",
1334 : exchange_url),
1335 : TALER_JSON_pack_amount ("amount",
1336 : &wired),
1337 : GNUNET_JSON_pack_timestamp ("execution_time",
1338 : execution_time),
1339 : GNUNET_JSON_pack_bool ("confirmed",
1340 : transfer_confirmed))));
1341 : }
1342 :
1343 :
1344 : /**
1345 : * Check transfer status in local database.
1346 : *
1347 : * @param[in,out] gorc order context to update
1348 : */
1349 : static void
1350 38 : phase_check_local_transfers (struct GetOrderRequestContext *gorc)
1351 : {
1352 38 : struct TMH_HandlerContext *hc = gorc->hc;
1353 : enum GNUNET_DB_QueryStatus qs;
1354 :
1355 38 : GNUNET_assert (gorc->paid);
1356 38 : GNUNET_assert (! gorc->order_only);
1357 :
1358 38 : GNUNET_assert (GNUNET_OK ==
1359 : TALER_amount_set_zero (gorc->contract_amount.currency,
1360 : &gorc->deposits_total));
1361 38 : GNUNET_assert (GNUNET_OK ==
1362 : TALER_amount_set_zero (gorc->contract_amount.currency,
1363 : &gorc->deposit_fees_total));
1364 :
1365 38 : qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
1366 : gorc->order_serial,
1367 : &process_transfer_details,
1368 : gorc);
1369 38 : if (0 > qs)
1370 : {
1371 0 : GNUNET_break (0);
1372 0 : phase_end (gorc,
1373 : TALER_MHD_reply_with_error (gorc->sc.con,
1374 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1375 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1376 : "transfer details"));
1377 0 : return;
1378 : }
1379 38 : if (gorc->deposit_currency_mismatch)
1380 : {
1381 0 : GNUNET_break (0);
1382 0 : phase_end (gorc,
1383 : TALER_MHD_reply_with_error (gorc->sc.con,
1384 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1385 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1386 : "deposits in different currency than original order price"));
1387 0 : return;
1388 : }
1389 :
1390 38 : if (! gorc->wired)
1391 : {
1392 : /* we believe(d) the wire transfer did not happen yet, check if maybe
1393 : in light of new evidence it did */
1394 : struct TALER_Amount expect_total;
1395 :
1396 24 : if (0 >
1397 24 : TALER_amount_subtract (&expect_total,
1398 24 : &gorc->contract_amount,
1399 24 : &gorc->refund_amount))
1400 : {
1401 0 : GNUNET_break (0);
1402 0 : phase_end (gorc,
1403 : TALER_MHD_reply_with_error (
1404 : gorc->sc.con,
1405 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1406 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1407 : "refund exceeds contract value"));
1408 0 : return;
1409 : }
1410 24 : if (0 >
1411 24 : TALER_amount_subtract (&expect_total,
1412 : &expect_total,
1413 24 : &gorc->deposit_fees_total))
1414 : {
1415 0 : GNUNET_break (0);
1416 0 : phase_end (gorc,
1417 : TALER_MHD_reply_with_error (
1418 : gorc->sc.con,
1419 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1420 : TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
1421 : "deposit fees exceed total minus refunds"));
1422 0 : return;
1423 : }
1424 24 : if (0 >=
1425 24 : TALER_amount_cmp (&expect_total,
1426 24 : &gorc->deposits_total))
1427 : {
1428 : /* expect_total <= gorc->deposits_total: good: we got the wire transfer */
1429 0 : gorc->wired = true;
1430 0 : qs = TMH_db->mark_order_wired (TMH_db->cls,
1431 : gorc->order_serial);
1432 0 : GNUNET_break (qs >= 0); /* just warn if transaction failed */
1433 0 : TMH_notify_order_change (hc->instance,
1434 : TMH_OSF_PAID
1435 : | TMH_OSF_WIRED,
1436 0 : gorc->contract_terms->timestamp,
1437 : gorc->order_serial);
1438 : }
1439 : }
1440 38 : gorc->phase++;
1441 : }
1442 :
1443 :
1444 : /**
1445 : * Generate final result for the status request.
1446 : *
1447 : * @param[in,out] gorc order context to update
1448 : */
1449 : static void
1450 38 : phase_reply_result (struct GetOrderRequestContext *gorc)
1451 : {
1452 38 : struct TMH_HandlerContext *hc = gorc->hc;
1453 : MHD_RESULT ret;
1454 : char *order_status_url;
1455 :
1456 38 : GNUNET_assert (gorc->paid);
1457 38 : GNUNET_assert (! gorc->order_only);
1458 :
1459 : {
1460 38 : struct TALER_PrivateContractHashP *h_contract = NULL;
1461 :
1462 : /* In a session-bound payment, allow the browser to check the order
1463 : * status page (e.g. to get a refund).
1464 : *
1465 : * Note that we don't allow this outside of session-based payment, as
1466 : * otherwise this becomes an oracle to convert order_id to h_contract.
1467 : */
1468 38 : if (NULL != gorc->session_id)
1469 2 : h_contract = &gorc->h_contract_terms;
1470 :
1471 : order_status_url =
1472 38 : TMH_make_order_status_url (gorc->sc.con,
1473 38 : hc->infix,
1474 : gorc->session_id,
1475 38 : hc->instance->settings.id,
1476 : &gorc->claim_token,
1477 : h_contract);
1478 : }
1479 38 : if (GNUNET_TIME_absolute_is_zero (gorc->last_payment.abs_time))
1480 : {
1481 0 : GNUNET_break (GNUNET_YES ==
1482 : TALER_amount_is_zero (&gorc->contract_amount));
1483 0 : gorc->last_payment = gorc->contract_terms->timestamp;
1484 : }
1485 38 : ret = TALER_MHD_REPLY_JSON_PACK (
1486 : gorc->sc.con,
1487 : MHD_HTTP_OK,
1488 : // Deprecated in protocol v6.
1489 : GNUNET_JSON_pack_array_steal ("wire_reports",
1490 : json_array ()),
1491 : GNUNET_JSON_pack_uint64 ("exchange_code",
1492 : gorc->exchange_ec),
1493 : GNUNET_JSON_pack_uint64 ("exchange_http_status",
1494 : gorc->exchange_hc),
1495 : /* legacy: */
1496 : GNUNET_JSON_pack_uint64 ("exchange_ec",
1497 : gorc->exchange_ec),
1498 : /* legacy: */
1499 : GNUNET_JSON_pack_uint64 ("exchange_hc",
1500 : gorc->exchange_hc),
1501 : TALER_JSON_pack_amount ("deposit_total",
1502 : &gorc->deposits_total),
1503 : GNUNET_JSON_pack_object_incref ("contract_terms",
1504 : gorc->contract_terms_json),
1505 : GNUNET_JSON_pack_string ("order_status",
1506 : "paid"),
1507 : GNUNET_JSON_pack_timestamp ("last_payment",
1508 : gorc->last_payment),
1509 : GNUNET_JSON_pack_bool ("refunded",
1510 : gorc->refunded),
1511 : GNUNET_JSON_pack_bool ("wired",
1512 : gorc->wired),
1513 : GNUNET_JSON_pack_bool ("refund_pending",
1514 : gorc->refund_pending),
1515 : GNUNET_JSON_pack_allow_null (
1516 : TALER_JSON_pack_amount ("refund_amount",
1517 : &gorc->refund_amount)),
1518 : GNUNET_JSON_pack_array_steal ("wire_details",
1519 : gorc->wire_details),
1520 : GNUNET_JSON_pack_array_steal ("refund_details",
1521 : gorc->refund_details),
1522 : GNUNET_JSON_pack_string ("order_status_url",
1523 : order_status_url),
1524 : (gorc->choice_index >= 0)
1525 : ? GNUNET_JSON_pack_int64 ("choice_index",
1526 : gorc->choice_index)
1527 : : GNUNET_JSON_pack_end_ ());
1528 38 : GNUNET_free (order_status_url);
1529 38 : gorc->wire_details = NULL;
1530 38 : gorc->refund_details = NULL;
1531 38 : phase_end (gorc,
1532 : ret);
1533 38 : }
1534 :
1535 :
1536 : /**
1537 : * End with error status in wire_hc and wire_ec.
1538 : *
1539 : * @param[in,out] gorc order context to update
1540 : */
1541 : static void
1542 0 : phase_error (struct GetOrderRequestContext *gorc)
1543 : {
1544 0 : GNUNET_assert (TALER_EC_NONE != gorc->wire_ec);
1545 0 : phase_end (gorc,
1546 : TALER_MHD_reply_with_error (gorc->sc.con,
1547 : gorc->wire_hc,
1548 : gorc->wire_ec,
1549 : NULL));
1550 0 : }
1551 :
1552 :
1553 : MHD_RESULT
1554 66 : TMH_private_get_orders_ID (
1555 : const struct TMH_RequestHandler *rh,
1556 : struct MHD_Connection *connection,
1557 : struct TMH_HandlerContext *hc)
1558 : {
1559 66 : struct GetOrderRequestContext *gorc = hc->ctx;
1560 :
1561 66 : if (NULL == gorc)
1562 : {
1563 : /* First time here, parse request and check order is known */
1564 64 : GNUNET_assert (NULL != hc->infix);
1565 64 : gorc = GNUNET_new (struct GetOrderRequestContext);
1566 64 : hc->cc = &gorc_cleanup;
1567 64 : hc->ctx = gorc;
1568 64 : gorc->sc.con = connection;
1569 64 : gorc->hc = hc;
1570 64 : gorc->wire_details = json_array ();
1571 64 : GNUNET_assert (NULL != gorc->wire_details);
1572 64 : gorc->refund_details = json_array ();
1573 64 : GNUNET_assert (NULL != gorc->refund_details);
1574 64 : gorc->session_id = MHD_lookup_connection_value (connection,
1575 : MHD_GET_ARGUMENT_KIND,
1576 : "session_id");
1577 64 : if (! (TALER_MHD_arg_to_yna (connection,
1578 : "allow_refunded_for_repurchase",
1579 : TALER_EXCHANGE_YNA_NO,
1580 : &gorc->allow_refunded_for_repurchase)) )
1581 0 : return TALER_MHD_reply_with_error (connection,
1582 : MHD_HTTP_BAD_REQUEST,
1583 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1584 : "allow_refunded_for_repurchase");
1585 64 : TALER_MHD_parse_request_timeout (connection,
1586 : &gorc->sc.long_poll_timeout);
1587 64 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1588 : "Starting GET /private/orders/%s processing with timeout %s\n",
1589 : hc->infix,
1590 : GNUNET_STRINGS_absolute_time_to_string (
1591 : gorc->sc.long_poll_timeout));
1592 : }
1593 66 : if (GNUNET_SYSERR == gorc->suspended)
1594 0 : return MHD_NO; /* we are in shutdown */
1595 : while (1)
1596 : {
1597 1150 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1598 : "Processing order %s in phase %d\n",
1599 : hc->infix,
1600 : (int) gorc->phase);
1601 608 : switch (gorc->phase)
1602 : {
1603 64 : case GOP_INIT:
1604 64 : phase_init (gorc);
1605 64 : break;
1606 66 : case GOP_FETCH_CONTRACT:
1607 66 : phase_fetch_contract (gorc);
1608 66 : break;
1609 66 : case GOP_PARSE_CONTRACT:
1610 66 : phase_parse_contract (gorc);
1611 66 : break;
1612 66 : case GOP_CHECK_PAID:
1613 66 : phase_check_paid (gorc);
1614 66 : break;
1615 66 : case GOP_CHECK_REPURCHASE:
1616 66 : phase_check_repurchase (gorc);
1617 66 : break;
1618 62 : case GOP_UNPAID_FINISH:
1619 62 : phase_unpaid_finish (gorc);
1620 62 : break;
1621 38 : case GOP_CHECK_REFUNDS:
1622 38 : phase_check_refunds (gorc);
1623 38 : break;
1624 38 : case GOP_CHECK_DEPOSITS:
1625 38 : phase_check_deposits (gorc);
1626 38 : break;
1627 38 : case GOP_CHECK_LOCAL_TRANSFERS:
1628 38 : phase_check_local_transfers (gorc);
1629 38 : break;
1630 38 : case GOP_REPLY_RESULT:
1631 38 : phase_reply_result (gorc);
1632 38 : break;
1633 0 : case GOP_ERROR:
1634 0 : phase_error (gorc);
1635 0 : break;
1636 2 : case GOP_SUSPENDED_ON_UNPAID:
1637 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1638 : "Suspending order request awaiting payment\n");
1639 2 : return MHD_YES;
1640 64 : case GOP_END_YES:
1641 64 : return MHD_YES;
1642 0 : case GOP_END_NO:
1643 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1644 : "Closing connection, no response generated\n");
1645 0 : return MHD_NO;
1646 : }
1647 : } /* end first-time per-request initialization */
1648 : }
|