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 15 : TMH_force_wallet_refund_order_resume (void)
301 : {
302 : struct PostRefundData *prd;
303 :
304 15 : 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 15 : }
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 : switch (hr->http_status)
404 : {
405 4 : case MHD_HTTP_OK:
406 : {
407 : enum GNUNET_DB_QueryStatus qs;
408 :
409 4 : cr->exchange_pub = rr->details.ok.exchange_pub;
410 4 : cr->exchange_sig = rr->details.ok.exchange_sig;
411 4 : qs = TMH_db->insert_refund_proof (TMH_db->cls,
412 : cr->refund_serial,
413 : &rr->details.ok.exchange_sig,
414 : &rr->details.ok.exchange_pub);
415 4 : if (0 >= qs)
416 : {
417 : /* generally, this is relatively harmless for the merchant, but let's at
418 : least log this. */
419 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
420 : "Failed to persist exchange response to /refund in database: %d\n",
421 : qs);
422 : }
423 : else
424 : {
425 4 : notify_refund_obtained (cr->prd);
426 : }
427 : }
428 4 : break;
429 0 : default:
430 0 : cr->exchange_code = hr->ec;
431 0 : cr->exchange_reply = json_incref ((json_t*) hr->reply);
432 0 : break;
433 : }
434 4 : check_resume_prd (cr->prd);
435 4 : }
436 :
437 :
438 : /**
439 : * Function called with the result of a
440 : * #TMH_EXCHANGES_keys4exchange()
441 : * operation.
442 : *
443 : * @param cls a `struct CoinRefund *`
444 : * @param keys keys of exchange, NULL on error
445 : * @param exchange representation of the exchange
446 : */
447 : static void
448 4 : exchange_found_cb (void *cls,
449 : struct TALER_EXCHANGE_Keys *keys,
450 : struct TMH_Exchange *exchange)
451 : {
452 4 : struct CoinRefund *cr = cls;
453 4 : struct PostRefundData *prd = cr->prd;
454 :
455 : (void) exchange;
456 4 : cr->fo = NULL;
457 4 : if (NULL == keys)
458 : {
459 0 : prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
460 0 : prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
461 0 : check_resume_prd (prd);
462 0 : return;
463 : }
464 4 : cr->rh = TALER_EXCHANGE_refund (
465 : TMH_curl_ctx,
466 4 : cr->exchange_url,
467 : keys,
468 4 : &cr->refund_amount,
469 4 : &prd->h_contract_terms,
470 4 : &cr->coin_pub,
471 : cr->rtransaction_id,
472 4 : &prd->hc->instance->merchant_priv,
473 : &refund_cb,
474 : cr);
475 : }
476 :
477 :
478 : /**
479 : * Function called with information about a refund.
480 : * It is responsible for summing up the refund amount.
481 : *
482 : * @param cls closure
483 : * @param refund_serial unique serial number of the refund
484 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
485 : * @param coin_pub public coin from which the refund comes from
486 : * @param exchange_url URL of the exchange that issued @a coin_pub
487 : * @param rtransaction_id identificator of the refund
488 : * @param reason human-readable explanation of the refund
489 : * @param refund_amount refund amount which is being taken from @a coin_pub
490 : * @param pending true if the this refund was not yet processed by the wallet/exchange
491 : */
492 : static void
493 8 : process_refunds_cb (void *cls,
494 : uint64_t refund_serial,
495 : struct GNUNET_TIME_Timestamp timestamp,
496 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
497 : const char *exchange_url,
498 : uint64_t rtransaction_id,
499 : const char *reason,
500 : const struct TALER_Amount *refund_amount,
501 : bool pending)
502 : {
503 8 : struct PostRefundData *prd = cls;
504 : struct CoinRefund *cr;
505 :
506 8 : for (cr = prd->cr_head;
507 12 : NULL != cr;
508 4 : cr = cr->next)
509 8 : if (cr->refund_serial == refund_serial)
510 4 : return;
511 : /* already known */
512 :
513 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
514 : "Found refund of %s for coin %s with reason `%s' in database\n",
515 : TALER_amount2s (refund_amount),
516 : TALER_B2S (coin_pub),
517 : reason);
518 4 : cr = GNUNET_new (struct CoinRefund);
519 4 : cr->refund_serial = refund_serial;
520 4 : cr->exchange_url = GNUNET_strdup (exchange_url);
521 4 : cr->prd = prd;
522 4 : cr->coin_pub = *coin_pub;
523 4 : cr->rtransaction_id = rtransaction_id;
524 4 : cr->refund_amount = *refund_amount;
525 4 : cr->execution_time = timestamp;
526 4 : GNUNET_CONTAINER_DLL_insert (prd->cr_head,
527 : prd->cr_tail,
528 : cr);
529 4 : if (prd->refunded)
530 : {
531 2 : GNUNET_assert (0 <=
532 : TALER_amount_add (&prd->refund_amount,
533 : &prd->refund_amount,
534 : refund_amount));
535 2 : return;
536 : }
537 2 : prd->refund_amount = *refund_amount;
538 2 : prd->refunded = true;
539 2 : prd->refund_available |= pending;
540 : }
541 :
542 :
543 : /**
544 : * Obtain refunds for an order.
545 : *
546 : * @param rh context of the handler
547 : * @param connection the MHD connection to handle
548 : * @param[in,out] hc context with further information about the request
549 : * @return MHD result code
550 : */
551 : MHD_RESULT
552 4 : TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh,
553 : struct MHD_Connection *connection,
554 : struct TMH_HandlerContext *hc)
555 : {
556 4 : struct PostRefundData *prd = hc->ctx;
557 : enum GNUNET_DB_QueryStatus qs;
558 :
559 4 : if (NULL == prd)
560 : {
561 2 : prd = GNUNET_new (struct PostRefundData);
562 2 : prd->sc.con = connection;
563 2 : prd->hc = hc;
564 2 : prd->order_id = hc->infix;
565 2 : hc->ctx = prd;
566 2 : hc->cc = &refund_cleanup;
567 : {
568 : enum GNUNET_GenericReturnValue res;
569 :
570 : struct GNUNET_JSON_Specification spec[] = {
571 2 : GNUNET_JSON_spec_fixed_auto ("h_contract",
572 : &prd->h_contract_terms),
573 2 : GNUNET_JSON_spec_end ()
574 : };
575 2 : res = TALER_MHD_parse_json_data (connection,
576 2 : hc->request_body,
577 : spec);
578 2 : if (GNUNET_OK != res)
579 : return (GNUNET_NO == res)
580 : ? MHD_YES
581 0 : : MHD_NO;
582 : }
583 :
584 2 : TMH_db->preflight (TMH_db->cls);
585 : {
586 : json_t *contract_terms;
587 : uint64_t order_serial;
588 :
589 2 : qs = TMH_db->lookup_contract_terms (TMH_db->cls,
590 2 : hc->instance->settings.id,
591 2 : hc->infix,
592 : &contract_terms,
593 : &order_serial,
594 : NULL);
595 2 : if (0 > qs)
596 : {
597 : /* single, read-only SQL statements should never cause
598 : serialization problems */
599 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
600 : /* Always report on hard error as well to enable diagnostics */
601 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
602 0 : return TALER_MHD_reply_with_error (connection,
603 : MHD_HTTP_INTERNAL_SERVER_ERROR,
604 : TALER_EC_GENERIC_DB_FETCH_FAILED,
605 : "contract terms");
606 : }
607 2 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
608 : {
609 0 : json_decref (contract_terms);
610 0 : return TALER_MHD_reply_with_error (connection,
611 : MHD_HTTP_NOT_FOUND,
612 : TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
613 0 : hc->infix);
614 : }
615 : {
616 : struct TALER_PrivateContractHashP h_contract_terms;
617 :
618 2 : if (GNUNET_OK !=
619 2 : TALER_JSON_contract_hash (contract_terms,
620 : &h_contract_terms))
621 : {
622 0 : GNUNET_break (0);
623 0 : json_decref (contract_terms);
624 0 : return TALER_MHD_reply_with_error (connection,
625 : MHD_HTTP_INTERNAL_SERVER_ERROR,
626 : TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
627 : NULL);
628 : }
629 2 : json_decref (contract_terms);
630 2 : if (0 != GNUNET_memcmp (&h_contract_terms,
631 : &prd->h_contract_terms))
632 : {
633 0 : return TALER_MHD_reply_with_error (
634 : connection,
635 : MHD_HTTP_FORBIDDEN,
636 : TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
637 : NULL);
638 : }
639 : }
640 : }
641 : }
642 4 : if (GNUNET_SYSERR == prd->suspended)
643 0 : return MHD_NO; /* we are in shutdown */
644 :
645 4 : if (TALER_EC_NONE != prd->ec)
646 : {
647 0 : GNUNET_break (0 != prd->http_status);
648 : /* kill pending coin refund operations immediately, just to be
649 : extra sure they don't modify 'prd' after we already created
650 : a reply (this might not be needed, but feels safer). */
651 0 : for (struct CoinRefund *cr = prd->cr_head;
652 0 : NULL != cr;
653 0 : cr = cr->next)
654 : {
655 0 : if (NULL != cr->fo)
656 : {
657 0 : TMH_EXCHANGES_keys4exchange_cancel (cr->fo);
658 0 : cr->fo = NULL;
659 : }
660 0 : if (NULL != cr->rh)
661 : {
662 0 : TALER_EXCHANGE_refund_cancel (cr->rh);
663 0 : cr->rh = NULL;
664 : }
665 : }
666 0 : return TALER_MHD_reply_with_error (connection,
667 : prd->http_status,
668 : prd->ec,
669 : NULL);
670 : }
671 :
672 4 : qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
673 4 : hc->instance->settings.id,
674 4 : &prd->h_contract_terms,
675 : &process_refunds_cb,
676 : prd);
677 4 : if (0 > qs)
678 : {
679 0 : GNUNET_break (0);
680 0 : return TALER_MHD_reply_with_error (connection,
681 : MHD_HTTP_INTERNAL_SERVER_ERROR,
682 : TALER_EC_GENERIC_DB_FETCH_FAILED,
683 : "detailed refunds");
684 : }
685 4 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
686 : {
687 0 : GNUNET_break (0);
688 0 : return TALER_MHD_reply_with_error (connection,
689 : MHD_HTTP_INTERNAL_SERVER_ERROR,
690 : TALER_EC_GENERIC_DB_FETCH_FAILED,
691 : "no coins found that could be refunded");
692 : }
693 :
694 : /* Now launch exchange interactions, unless we already have the
695 : response in the database! */
696 4 : for (struct CoinRefund *cr = prd->cr_head;
697 12 : NULL != cr;
698 8 : cr = cr->next)
699 : {
700 8 : qs = TMH_db->lookup_refund_proof (TMH_db->cls,
701 : cr->refund_serial,
702 : &cr->exchange_sig,
703 : &cr->exchange_pub);
704 8 : switch (qs)
705 : {
706 0 : case GNUNET_DB_STATUS_HARD_ERROR:
707 : case GNUNET_DB_STATUS_SOFT_ERROR:
708 0 : return TALER_MHD_reply_with_error (connection,
709 : MHD_HTTP_INTERNAL_SERVER_ERROR,
710 : TALER_EC_GENERIC_DB_FETCH_FAILED,
711 : "refund proof");
712 4 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
713 4 : if (NULL == cr->exchange_reply)
714 : {
715 : /* We need to talk to the exchange */
716 4 : cr->fo = TMH_EXCHANGES_keys4exchange (cr->exchange_url,
717 : false,
718 : &exchange_found_cb,
719 : cr);
720 4 : if (NULL == cr->fo)
721 : {
722 0 : GNUNET_break (0);
723 0 : return TALER_MHD_reply_with_error (connection,
724 : MHD_HTTP_INTERNAL_SERVER_ERROR,
725 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
726 0 : cr->exchange_url);
727 : }
728 : }
729 4 : break;
730 4 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
731 : /* We got a reply earlier, set status accordingly */
732 4 : cr->exchange_status = MHD_HTTP_OK;
733 4 : break;
734 : }
735 : }
736 :
737 : /* Check if there are still exchange operations pending */
738 4 : if (exchange_operations_pending (prd))
739 : {
740 2 : if (GNUNET_NO == prd->suspended)
741 : {
742 2 : prd->suspended = GNUNET_YES;
743 2 : MHD_suspend_connection (connection);
744 2 : GNUNET_CONTAINER_DLL_insert (prd_head,
745 : prd_tail,
746 : prd);
747 : }
748 2 : return MHD_YES; /* we're still talking to the exchange */
749 : }
750 :
751 : {
752 : json_t *ra;
753 :
754 2 : ra = json_array ();
755 2 : GNUNET_assert (NULL != ra);
756 2 : for (struct CoinRefund *cr = prd->cr_head;
757 6 : NULL != cr;
758 4 : cr = cr->next)
759 : {
760 : json_t *refund;
761 :
762 4 : if (MHD_HTTP_OK != cr->exchange_status)
763 : {
764 0 : if (NULL == cr->exchange_reply)
765 : {
766 0 : refund = GNUNET_JSON_PACK (
767 : GNUNET_JSON_pack_string ("type",
768 : "failure"),
769 : GNUNET_JSON_pack_uint64 ("exchange_status",
770 : cr->exchange_status),
771 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
772 : cr->rtransaction_id),
773 : GNUNET_JSON_pack_data_auto ("coin_pub",
774 : &cr->coin_pub),
775 : TALER_JSON_pack_amount ("refund_amount",
776 : &cr->refund_amount),
777 : GNUNET_JSON_pack_timestamp ("execution_time",
778 : cr->execution_time));
779 : }
780 : else
781 : {
782 0 : refund = GNUNET_JSON_PACK (
783 : GNUNET_JSON_pack_string ("type",
784 : "failure"),
785 : GNUNET_JSON_pack_uint64 ("exchange_status",
786 : cr->exchange_status),
787 : GNUNET_JSON_pack_uint64 ("exchange_code",
788 : cr->exchange_code),
789 : GNUNET_JSON_pack_object_incref ("exchange_reply",
790 : cr->exchange_reply),
791 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
792 : cr->rtransaction_id),
793 : GNUNET_JSON_pack_data_auto ("coin_pub",
794 : &cr->coin_pub),
795 : TALER_JSON_pack_amount ("refund_amount",
796 : &cr->refund_amount),
797 : GNUNET_JSON_pack_timestamp ("execution_time",
798 : cr->execution_time));
799 : }
800 : }
801 : else
802 : {
803 4 : refund = GNUNET_JSON_PACK (
804 : GNUNET_JSON_pack_string ("type",
805 : "success"),
806 : GNUNET_JSON_pack_uint64 ("exchange_status",
807 : cr->exchange_status),
808 : GNUNET_JSON_pack_data_auto ("exchange_sig",
809 : &cr->exchange_sig),
810 : GNUNET_JSON_pack_data_auto ("exchange_pub",
811 : &cr->exchange_pub),
812 : GNUNET_JSON_pack_uint64 ("rtransaction_id",
813 : cr->rtransaction_id),
814 : GNUNET_JSON_pack_data_auto ("coin_pub",
815 : &cr->coin_pub),
816 : TALER_JSON_pack_amount ("refund_amount",
817 : &cr->refund_amount),
818 : GNUNET_JSON_pack_timestamp ("execution_time",
819 : cr->execution_time));
820 : }
821 4 : GNUNET_assert (
822 : 0 ==
823 : json_array_append_new (ra,
824 : refund));
825 : }
826 :
827 2 : return TALER_MHD_REPLY_JSON_PACK (
828 : connection,
829 : MHD_HTTP_OK,
830 : TALER_JSON_pack_amount ("refund_amount",
831 : &prd->refund_amount),
832 : GNUNET_JSON_pack_array_steal ("refunds",
833 : ra),
834 : GNUNET_JSON_pack_data_auto ("merchant_pub",
835 : &hc->instance->merchant_pub));
836 : }
837 :
838 : return MHD_YES;
839 : }
840 :
841 :
842 : /* end of taler-merchant-httpd_post-orders-ID-refund.c */
|