Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2020-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Affero General Public License as
7 : published by the Free Software Foundation; either version 3,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 :
20 : /**
21 : * @file taler-merchant-httpd_post-orders-ID-refund.c
22 : * @brief handling of POST /orders/$ID/refund requests
23 : * @author Jonathan Buchanan
24 : */
25 : #include "platform.h"
26 : #include <taler/taler_dbevents.h>
27 : #include <taler/taler_signatures.h>
28 : #include <taler/taler_json_lib.h>
29 : #include <taler/taler_exchange_service.h>
30 : #include "taler-merchant-httpd.h"
31 : #include "taler-merchant-httpd_auditors.h"
32 : #include "taler-merchant-httpd_exchanges.h"
33 : #include "taler-merchant-httpd_post-orders-ID-refund.h"
34 :
35 :
36 : /**
37 : * Information we keep for each coin to be refunded.
38 : */
39 : struct CoinRefund
40 : {
41 :
42 : /**
43 : * Kept in a DLL.
44 : */
45 : struct CoinRefund *next;
46 :
47 : /**
48 : * Kept in a DLL.
49 : */
50 : struct CoinRefund *prev;
51 :
52 : /**
53 : * Request to connect to the target exchange.
54 : */
55 : struct TMH_EXCHANGES_FindOperation *fo;
56 :
57 : /**
58 : * Handle for the refund operation with the exchange.
59 : */
60 : struct TALER_EXCHANGE_RefundHandle *rh;
61 :
62 : /**
63 : * Request this operation is part of.
64 : */
65 : struct PostRefundData *prd;
66 :
67 : /**
68 : * URL of the exchange for this @e coin_pub.
69 : */
70 : char *exchange_url;
71 :
72 : /**
73 : * Fully reply from the exchange, only possibly set if
74 : * we got a JSON reply and a non-#MHD_HTTP_OK error code
75 : */
76 : json_t *exchange_reply;
77 :
78 : /**
79 : * When did the merchant grant the refund. To be used to group events
80 : * in the wallet.
81 : */
82 : struct GNUNET_TIME_Timestamp execution_time;
83 :
84 : /**
85 : * Coin to refund.
86 : */
87 : struct TALER_CoinSpendPublicKeyP coin_pub;
88 :
89 : /**
90 : * Refund transaction ID to use.
91 : */
92 : uint64_t rtransaction_id;
93 :
94 : /**
95 : * Unique serial number identifying the refund.
96 : */
97 : uint64_t refund_serial;
98 :
99 : /**
100 : * Amount to refund.
101 : */
102 : struct TALER_Amount refund_amount;
103 :
104 : /**
105 : * Public key of the exchange affirming the refund.
106 : */
107 : struct TALER_ExchangePublicKeyP exchange_pub;
108 :
109 : /**
110 : * Signature of the exchange affirming the refund.
111 : */
112 : struct TALER_ExchangeSignatureP exchange_sig;
113 :
114 : /**
115 : * HTTP status from the exchange, #MHD_HTTP_OK if
116 : * @a exchange_pub and @a exchange_sig are valid.
117 : */
118 : unsigned int exchange_status;
119 :
120 : /**
121 : * HTTP error code from the exchange.
122 : */
123 : enum TALER_ErrorCode exchange_code;
124 :
125 : };
126 :
127 :
128 : /**
129 : * Context for the operation.
130 : */
131 : struct PostRefundData
132 : {
133 :
134 : /**
135 : * Hashed version of contract terms. All zeros if not provided.
136 : */
137 : struct TALER_PrivateContractHashP h_contract_terms;
138 :
139 : /**
140 : * DLL of (suspended) requests.
141 : */
142 : struct PostRefundData *next;
143 :
144 : /**
145 : * DLL of (suspended) requests.
146 : */
147 : struct PostRefundData *prev;
148 :
149 : /**
150 : * Refunds for this order. Head of DLL.
151 : */
152 : struct CoinRefund *cr_head;
153 :
154 : /**
155 : * Refunds for this order. Tail of DLL.
156 : */
157 : struct CoinRefund *cr_tail;
158 :
159 : /**
160 : * Context of the request.
161 : */
162 : struct TMH_HandlerContext *hc;
163 :
164 : /**
165 : * Entry in the #resume_timeout_heap for this check payment, if we are
166 : * suspended.
167 : */
168 : struct TMH_SuspendedConnection sc;
169 :
170 : /**
171 : * order ID for the payment
172 : */
173 : const char *order_id;
174 :
175 : /**
176 : * Where to get the contract
177 : */
178 : const char *contract_url;
179 :
180 : /**
181 : * fulfillment URL of the contract (valid as long as
182 : * @e contract_terms is valid).
183 : */
184 : const char *fulfillment_url;
185 :
186 : /**
187 : * session of the client
188 : */
189 : const char *session_id;
190 :
191 : /**
192 : * Contract terms of the payment we are checking. NULL when they
193 : * are not (yet) known.
194 : */
195 : json_t *contract_terms;
196 :
197 : /**
198 : * Total refunds granted for this payment. Only initialized
199 : * if @e refunded is set to true.
200 : */
201 : struct TALER_Amount refund_amount;
202 :
203 : /**
204 : * Did we suspend @a connection and are thus in
205 : * the #prd_head DLL (#GNUNET_YES). Set to
206 : * #GNUNET_NO if we are not suspended, and to
207 : * #GNUNET_SYSERR if we should close the connection
208 : * without a response due to shutdown.
209 : */
210 : enum GNUNET_GenericReturnValue suspended;
211 :
212 : /**
213 : * Return code: #TALER_EC_NONE if successful.
214 : */
215 : enum TALER_ErrorCode ec;
216 :
217 : /**
218 : * HTTP status to use for the reply, 0 if not yet known.
219 : */
220 : unsigned int http_status;
221 :
222 : /**
223 : * Set to true if we are dealing with an unclaimed order
224 : * (and thus @e h_contract_terms is not set, and certain
225 : * DB queries will not work).
226 : */
227 : bool unclaimed;
228 :
229 : /**
230 : * Set to true if this payment has been refunded and
231 : * @e refund_amount is initialized.
232 : */
233 : bool refunded;
234 :
235 : /**
236 : * Set to true if a refund is still available for the
237 : * wallet for this payment.
238 : */
239 : bool refund_available;
240 :
241 : /**
242 : * Set to true if the client requested HTML, otherwise
243 : * we generate JSON.
244 : */
245 : bool generate_html;
246 :
247 : };
248 :
249 :
250 : /**
251 : * Head of DLL of (suspended) requests.
252 : */
253 : static struct PostRefundData *prd_head;
254 :
255 : /**
256 : * Tail of DLL of (suspended) requests.
257 : */
258 : static struct PostRefundData *prd_tail;
259 :
260 :
261 : /**
262 : * Function called when we are done processing a refund request.
263 : * Frees memory associated with @a ctx.
264 : *
265 : * @param ctx a `struct PostRefundData`
266 : */
267 : static void
268 0 : refund_cleanup (void *ctx)
269 : {
270 0 : struct PostRefundData *prd = ctx;
271 : struct CoinRefund *cr;
272 :
273 0 : while (NULL != (cr = prd->cr_head))
274 : {
275 0 : GNUNET_CONTAINER_DLL_remove (prd->cr_head,
276 : prd->cr_tail,
277 : cr);
278 0 : json_decref (cr->exchange_reply);
279 0 : GNUNET_free (cr->exchange_url);
280 0 : if (NULL != cr->fo)
281 : {
282 0 : TMH_EXCHANGES_find_exchange_cancel (cr->fo);
283 0 : cr->fo = NULL;
284 : }
285 0 : if (NULL != cr->rh)
286 : {
287 0 : TALER_EXCHANGE_refund_cancel (cr->rh);
288 0 : cr->rh = NULL;
289 : }
290 0 : GNUNET_free (cr);
291 : }
292 0 : json_decref (prd->contract_terms);
293 0 : GNUNET_free (prd);
294 0 : }
295 :
296 :
297 : /**
298 : * Force resuming all suspended order lookups, needed during shutdown.
299 : */
300 : void
301 0 : TMH_force_wallet_refund_order_resume (void)
302 : {
303 : struct PostRefundData *prd;
304 :
305 0 : while (NULL != (prd = prd_head))
306 : {
307 0 : GNUNET_CONTAINER_DLL_remove (prd_head,
308 : prd_tail,
309 : prd);
310 0 : GNUNET_assert (GNUNET_YES == prd->suspended);
311 0 : prd->suspended = GNUNET_SYSERR;
312 0 : MHD_resume_connection (prd->sc.con);
313 : }
314 0 : }
315 :
316 :
317 : /**
318 : * Check if @a prd has exchange requests still pending.
319 : *
320 : * @param prd state to check
321 : * @return true if activities are still pending
322 : */
323 : static bool
324 0 : exchange_operations_pending (struct PostRefundData *prd)
325 : {
326 0 : for (struct CoinRefund *cr = prd->cr_head;
327 : NULL != cr;
328 0 : cr = cr->next)
329 : {
330 0 : if ( (NULL != cr->fo) ||
331 0 : (NULL != cr->rh) )
332 0 : return true;
333 : }
334 0 : return false;
335 : }
336 :
337 :
338 : /**
339 : * Check if @a prd is ready to be resumed, and if so, do it.
340 : *
341 : * @param prd refund request to be possibly ready
342 : */
343 : static void
344 0 : check_resume_prd (struct PostRefundData *prd)
345 : {
346 0 : if ( (TALER_EC_NONE == prd->ec) &&
347 0 : exchange_operations_pending (prd) )
348 0 : return;
349 0 : GNUNET_CONTAINER_DLL_remove (prd_head,
350 : prd_tail,
351 : prd);
352 0 : GNUNET_assert (prd->suspended);
353 0 : prd->suspended = GNUNET_NO;
354 0 : MHD_resume_connection (prd->sc.con);
355 0 : TALER_MHD_daemon_trigger ();
356 : }
357 :
358 :
359 : /**
360 : * Notify applications waiting for a client to obtain
361 : * a refund.
362 : *
363 : * @param prd refund request with the change
364 : */
365 : static void
366 0 : notify_refund_obtained (struct PostRefundData *prd)
367 : {
368 0 : struct TMH_OrderPayEventP refund_eh = {
369 0 : .header.size = htons (sizeof (refund_eh)),
370 0 : .header.type = htons (TALER_DBEVENT_MERCHANT_REFUND_OBTAINED),
371 0 : .merchant_pub = prd->hc->instance->merchant_pub
372 : };
373 :
374 0 : GNUNET_CRYPTO_hash (prd->order_id,
375 : strlen (prd->order_id),
376 : &refund_eh.h_order_id);
377 0 : TMH_db->event_notify (TMH_db->cls,
378 : &refund_eh.header,
379 : NULL,
380 : 0);
381 0 : }
382 :
383 :
384 : /**
385 : * Callbacks of this type are used to serve the result of submitting a
386 : * refund request to an exchange.
387 : *
388 : * @param cls a `struct CoinRefund`
389 : * @param hr HTTP response data
390 : * @param exchange_pub exchange key used to sign refund confirmation
391 : * @param exchange_sig exchange's signature over refund
392 : */
393 : static void
394 0 : refund_cb (void *cls,
395 : const struct TALER_EXCHANGE_HttpResponse *hr,
396 : const struct TALER_ExchangePublicKeyP *exchange_pub,
397 : const struct TALER_ExchangeSignatureP *exchange_sig)
398 : {
399 0 : struct CoinRefund *cr = cls;
400 :
401 0 : cr->rh = NULL;
402 0 : cr->exchange_status = hr->http_status;
403 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
404 : "Exchange refund status for coin %s is %u\n",
405 : TALER_B2S (&cr->coin_pub),
406 : hr->http_status);
407 0 : if (MHD_HTTP_OK != hr->http_status)
408 : {
409 0 : cr->exchange_code = hr->ec;
410 0 : cr->exchange_reply = json_incref ((json_t*) hr->reply);
411 : }
412 : else
413 : {
414 : enum GNUNET_DB_QueryStatus qs;
415 :
416 0 : cr->exchange_pub = *exchange_pub;
417 0 : cr->exchange_sig = *exchange_sig;
418 0 : qs = TMH_db->insert_refund_proof (TMH_db->cls,
419 : cr->refund_serial,
420 : exchange_sig,
421 : exchange_pub);
422 0 : if (0 >= qs)
423 : {
424 : /* generally, this is relatively harmless for the merchant, but let's at
425 : least log this. */
426 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
427 : "Failed to persist exchange response to /refund in database: %d\n",
428 : qs);
429 : }
430 : else
431 : {
432 0 : notify_refund_obtained (cr->prd);
433 : }
434 : }
435 0 : check_resume_prd (cr->prd);
436 0 : }
437 :
438 :
439 : /**
440 : * Function called with the result of a #TMH_EXCHANGES_find_exchange()
441 : * operation.
442 : *
443 : * @param cls a `struct CoinRefund *`
444 : * @param hr HTTP response details
445 : * @param eh handle to the exchange context
446 : * @param payto_uri payto://-URI of the exchange
447 : * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
448 : * @param exchange_trusted true if this exchange is trusted by config
449 : */
450 : static void
451 0 : exchange_found_cb (void *cls,
452 : const struct TALER_EXCHANGE_HttpResponse *hr,
453 : struct TALER_EXCHANGE_Handle *eh,
454 : const char *payto_uri,
455 : const struct TALER_Amount *wire_fee,
456 : bool exchange_trusted)
457 : {
458 0 : struct CoinRefund *cr = cls;
459 0 : struct PostRefundData *prd = cr->prd;
460 :
461 : (void) payto_uri;
462 : (void) wire_fee;
463 : (void) exchange_trusted;
464 0 : cr->fo = NULL;
465 0 : if (NULL == hr)
466 : {
467 0 : prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
468 0 : prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
469 0 : check_resume_prd (prd);
470 0 : return;
471 : }
472 0 : if (NULL == eh)
473 : {
474 0 : prd->http_status = MHD_HTTP_BAD_GATEWAY;
475 0 : prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE;
476 0 : check_resume_prd (prd);
477 0 : return;
478 : }
479 0 : cr->rh = TALER_EXCHANGE_refund (eh,
480 0 : &cr->refund_amount,
481 0 : &prd->h_contract_terms,
482 0 : &cr->coin_pub,
483 : cr->rtransaction_id,
484 0 : &prd->hc->instance->merchant_priv,
485 : &refund_cb,
486 : cr);
487 : }
488 :
489 :
490 : /**
491 : * Function called with information about a refund.
492 : * It is responsible for summing up the refund amount.
493 : *
494 : * @param cls closure
495 : * @param refund_serial unique serial number of the refund
496 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
497 : * @param coin_pub public coin from which the refund comes from
498 : * @param exchange_url URL of the exchange that issued @a coin_pub
499 : * @param rtransaction_id identificator of the refund
500 : * @param reason human-readable explanation of the refund
501 : * @param refund_amount refund amount which is being taken from @a coin_pub
502 : * @param pending true if the this refund was not yet processed by the wallet/exchange
503 : */
504 : static void
505 0 : process_refunds_cb (void *cls,
506 : uint64_t refund_serial,
507 : struct GNUNET_TIME_Timestamp timestamp,
508 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
509 : const char *exchange_url,
510 : uint64_t rtransaction_id,
511 : const char *reason,
512 : const struct TALER_Amount *refund_amount,
513 : bool pending)
514 : {
515 0 : struct PostRefundData *prd = cls;
516 : struct CoinRefund *cr;
517 :
518 0 : for (cr = prd->cr_head;
519 : NULL != cr;
520 0 : cr = cr->next)
521 0 : if (cr->refund_serial == refund_serial)
522 0 : return;
523 : /* already known */
524 :
525 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
526 : "Found refund of %s for coin %s with reason `%s' in database\n",
527 : TALER_amount2s (refund_amount),
528 : TALER_B2S (coin_pub),
529 : reason);
530 0 : cr = GNUNET_new (struct CoinRefund);
531 0 : cr->refund_serial = refund_serial;
532 0 : cr->exchange_url = GNUNET_strdup (exchange_url);
533 0 : cr->prd = prd;
534 0 : cr->coin_pub = *coin_pub;
535 0 : cr->rtransaction_id = rtransaction_id;
536 0 : cr->refund_amount = *refund_amount;
537 0 : cr->execution_time = timestamp;
538 0 : GNUNET_CONTAINER_DLL_insert (prd->cr_head,
539 : prd->cr_tail,
540 : cr);
541 0 : if (prd->refunded)
542 : {
543 0 : GNUNET_assert (0 <=
544 : TALER_amount_add (&prd->refund_amount,
545 : &prd->refund_amount,
546 : refund_amount));
547 0 : return;
548 : }
549 0 : prd->refund_amount = *refund_amount;
550 0 : prd->refunded = true;
551 0 : prd->refund_available |= pending;
552 : }
553 :
554 :
555 : /**
556 : * Obtain refunds for an order.
557 : *
558 : * @param rh context of the handler
559 : * @param connection the MHD connection to handle
560 : * @param[in,out] hc context with further information about the request
561 : * @return MHD result code
562 : */
563 : MHD_RESULT
564 0 : TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
565 : struct MHD_Connection *connection,
566 : struct TMH_HandlerContext *hc)
567 : {
568 0 : struct PostRefundData *prd = hc->ctx;
569 : enum GNUNET_DB_QueryStatus qs;
570 :
571 0 : if (NULL == prd)
572 : {
573 0 : prd = GNUNET_new (struct PostRefundData);
574 0 : prd->sc.con = connection;
575 0 : prd->hc = hc;
576 0 : prd->order_id = hc->infix;
577 0 : hc->ctx = prd;
578 0 : hc->cc = &refund_cleanup;
579 : {
580 : enum GNUNET_GenericReturnValue res;
581 :
582 : struct GNUNET_JSON_Specification spec[] = {
583 0 : GNUNET_JSON_spec_fixed_auto ("h_contract", &prd->h_contract_terms),
584 0 : GNUNET_JSON_spec_end ()
585 : };
586 0 : res = TALER_MHD_parse_json_data (connection,
587 0 : hc->request_body,
588 : spec);
589 0 : if (GNUNET_OK != res)
590 : return (GNUNET_NO == res)
591 : ? MHD_YES
592 0 : : MHD_NO;
593 : }
594 :
595 0 : TMH_db->preflight (TMH_db->cls);
596 : {
597 : json_t *contract_terms;
598 : uint64_t order_serial;
599 : bool paid;
600 :
601 0 : qs = TMH_db->lookup_contract_terms (TMH_db->cls,
602 0 : hc->instance->settings.id,
603 0 : hc->infix,
604 : &contract_terms,
605 : &order_serial,
606 : &paid,
607 : NULL);
608 0 : if (0 > qs)
609 : {
610 : /* single, read-only SQL statements should never cause
611 : serialization problems */
612 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
613 : /* Always report on hard error as well to enable diagnostics */
614 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
615 0 : return TALER_MHD_reply_with_error (connection,
616 : MHD_HTTP_INTERNAL_SERVER_ERROR,
617 : TALER_EC_GENERIC_DB_FETCH_FAILED,
618 : "contract terms");
619 : }
620 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
621 : {
622 0 : json_decref (contract_terms);
623 0 : return TALER_MHD_reply_with_error (connection,
624 : MHD_HTTP_NOT_FOUND,
625 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
626 0 : hc->infix);
627 : }
628 : {
629 : struct TALER_PrivateContractHashP h_contract_terms;
630 :
631 0 : if (GNUNET_OK !=
632 0 : TALER_JSON_contract_hash (contract_terms,
633 : &h_contract_terms))
634 : {
635 0 : GNUNET_break (0);
636 0 : json_decref (contract_terms);
637 0 : return TALER_MHD_reply_with_error (connection,
638 : MHD_HTTP_INTERNAL_SERVER_ERROR,
639 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
640 : NULL);
641 : }
642 0 : json_decref (contract_terms);
643 0 : if (0 != GNUNET_memcmp (&h_contract_terms,
644 : &prd->h_contract_terms))
645 : {
646 0 : return TALER_MHD_reply_with_error (
647 : connection,
648 : MHD_HTTP_FORBIDDEN,
649 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
650 : NULL);
651 : }
652 : }
653 : }
654 : }
655 0 : if (GNUNET_SYSERR == prd->suspended)
656 0 : return MHD_NO; /* we are in shutdown */
657 :
658 0 : if (TALER_EC_NONE != prd->ec)
659 : {
660 0 : GNUNET_break (0 != prd->http_status);
661 : /* kill pending coin refund operations immediately, just to be
662 : extra sure they don't modify 'prd' after we already created
663 : a reply (this might not be needed, but feels safer). */
664 0 : for (struct CoinRefund *cr = prd->cr_head;
665 : NULL != cr;
666 0 : cr = cr->next)
667 : {
668 0 : if (NULL != cr->fo)
669 : {
670 0 : TMH_EXCHANGES_find_exchange_cancel (cr->fo);
671 0 : cr->fo = NULL;
672 : }
673 0 : if (NULL != cr->rh)
674 : {
675 0 : TALER_EXCHANGE_refund_cancel (cr->rh);
676 0 : cr->rh = NULL;
677 : }
678 : }
679 0 : return TALER_MHD_reply_with_error (connection,
680 : prd->http_status,
681 : prd->ec,
682 : NULL);
683 : }
684 :
685 : {
686 0 : GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (TMH_currency,
687 : &prd->refund_amount));
688 0 : qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
689 0 : hc->instance->settings.id,
690 0 : &prd->h_contract_terms,
691 : &process_refunds_cb,
692 : prd);
693 0 : if (0 > qs)
694 : {
695 0 : GNUNET_break (0);
696 0 : return TALER_MHD_reply_with_error (connection,
697 : MHD_HTTP_INTERNAL_SERVER_ERROR,
698 : TALER_EC_GENERIC_DB_FETCH_FAILED,
699 : "detailed refunds");
700 : }
701 : }
702 :
703 : /* Now launch exchange interactions, unless we already have the
704 : response in the database! */
705 0 : for (struct CoinRefund *cr = prd->cr_head;
706 : NULL != cr;
707 0 : cr = cr->next)
708 : {
709 : enum GNUNET_DB_QueryStatus qs;
710 :
711 0 : qs = TMH_db->lookup_refund_proof (TMH_db->cls,
712 : cr->refund_serial,
713 : &cr->exchange_sig,
714 : &cr->exchange_pub);
715 0 : switch (qs)
716 : {
717 0 : case GNUNET_DB_STATUS_HARD_ERROR:
718 : case GNUNET_DB_STATUS_SOFT_ERROR:
719 0 : return TALER_MHD_reply_with_error (connection,
720 : MHD_HTTP_INTERNAL_SERVER_ERROR,
721 : TALER_EC_GENERIC_DB_FETCH_FAILED,
722 : "refund proof");
723 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
724 0 : if (NULL == cr->exchange_reply)
725 : {
726 : /* We need to talk to the exchange */
727 0 : cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
728 : NULL,
729 : GNUNET_NO,
730 : &exchange_found_cb,
731 : cr);
732 : }
733 0 : break;
734 0 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
735 : /* We got a reply earlier, set status accordingly */
736 0 : cr->exchange_status = MHD_HTTP_OK;
737 0 : break;
738 : }
739 0 : }
740 :
741 : /* Check if there are still exchange operations pending */
742 0 : if (exchange_operations_pending (prd))
743 : {
744 0 : if (GNUNET_NO == prd->suspended)
745 : {
746 0 : prd->suspended = GNUNET_YES;
747 0 : MHD_suspend_connection (connection);
748 0 : GNUNET_CONTAINER_DLL_insert (prd_head,
749 : prd_tail,
750 : prd);
751 : }
752 0 : return MHD_YES; /* we're still talking to the exchange */
753 : }
754 :
755 : {
756 : json_t *ra;
757 :
758 0 : ra = json_array ();
759 0 : GNUNET_assert (NULL != ra);
760 0 : for (struct CoinRefund *cr = prd->cr_head;
761 : NULL != cr;
762 0 : cr = cr->next)
763 : {
764 : json_t *refund;
765 :
766 0 : if (MHD_HTTP_OK != cr->exchange_status)
767 : {
768 0 : if (NULL == cr->exchange_reply)
769 : {
770 0 : refund = GNUNET_JSON_PACK (
771 : GNUNET_JSON_pack_string ("type",
772 : "failure"),
773 : GNUNET_JSON_pack_uint64 ("exchange_status",
774 : cr->exchange_status),
775 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
776 : cr->rtransaction_id),
777 : GNUNET_JSON_pack_data_auto ("coin_pub",
778 : &cr->coin_pub),
779 : TALER_JSON_pack_amount ("refund_amount",
780 : &cr->refund_amount),
781 : GNUNET_JSON_pack_timestamp ("execution_time",
782 : cr->execution_time));
783 : }
784 : else
785 : {
786 0 : refund = GNUNET_JSON_PACK (
787 : GNUNET_JSON_pack_string ("type",
788 : "failure"),
789 : GNUNET_JSON_pack_uint64 ("exchange_status",
790 : cr->exchange_status),
791 : GNUNET_JSON_pack_uint64 ("exchange_code",
792 : cr->exchange_code),
793 : GNUNET_JSON_pack_object_incref ("exchange_reply",
794 : cr->exchange_reply),
795 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
796 : cr->rtransaction_id),
797 : GNUNET_JSON_pack_data_auto ("coin_pub",
798 : &cr->coin_pub),
799 : TALER_JSON_pack_amount ("refund_amount",
800 : &cr->refund_amount),
801 : GNUNET_JSON_pack_timestamp ("execution_time",
802 : cr->execution_time));
803 : }
804 : }
805 : else
806 : {
807 0 : refund = GNUNET_JSON_PACK (
808 : GNUNET_JSON_pack_string ("type",
809 : "success"),
810 : GNUNET_JSON_pack_uint64 ("exchange_status",
811 : cr->exchange_status),
812 : GNUNET_JSON_pack_data_auto ("exchange_sig",
813 : &cr->exchange_sig),
814 : GNUNET_JSON_pack_data_auto ("exchange_pub",
815 : &cr->exchange_pub),
816 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
817 : cr->rtransaction_id),
818 : GNUNET_JSON_pack_data_auto ("coin_pub",
819 : &cr->coin_pub),
820 : TALER_JSON_pack_amount ("refund_amount",
821 : &cr->refund_amount),
822 : GNUNET_JSON_pack_timestamp ("execution_time",
823 : cr->execution_time));
824 : }
825 0 : GNUNET_assert (
826 : 0 ==
827 : json_array_append_new (ra,
828 : refund));
829 : }
830 :
831 0 : return TALER_MHD_REPLY_JSON_PACK (
832 : connection,
833 : MHD_HTTP_OK,
834 : TALER_JSON_pack_amount ("refund_amount",
835 : &prd->refund_amount),
836 : GNUNET_JSON_pack_array_steal ("refunds",
837 : ra),
838 : GNUNET_JSON_pack_data_auto ("merchant_pub",
839 : &hc->instance->merchant_pub));
840 : }
841 :
842 : return MHD_YES;
843 : }
844 :
845 :
846 : /* end of taler-merchant-httpd_post-orders-ID-refund.c */
|