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