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