LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-orders-ORDER_ID-refund.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 74.9 % 207 155
Test Date: 2026-09-04 23:42:01 Functions: 100.0 % 9 9

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

Generated by: LCOV version 2.0-1