merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-orders-ORDER_ID-abort.c (29693B)


      1 /*
      2   This file is part of TALER
      3   (C) 2014-2021 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  * @file taler-merchant-httpd_post-orders-ORDER_ID-abort.c
     21  * @brief handling of POST /orders/$ID/abort requests
     22  * @author Marcello Stanisci
     23  * @author Christian Grothoff
     24  * @author Florian Dold
     25  */
     26 #include "taler/platform.h"
     27 #include <taler/taler_json_lib.h>
     28 #include <taler/taler_exchange_service.h>
     29 #include "taler-merchant-httpd_exchanges.h"
     30 #include "taler-merchant-httpd_get-exchanges.h"
     31 #include "taler-merchant-httpd_helper.h"
     32 #include "taler-merchant-httpd_post-orders-ORDER_ID-abort.h"
     33 
     34 
     35 /**
     36  * How long to wait before giving up processing with the exchange?
     37  */
     38 #define ABORT_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
     39                                  GNUNET_TIME_UNIT_SECONDS, \
     40                                  30))
     41 
     42 /**
     43  * How often do we retry the (complex!) database transaction?
     44  */
     45 #define MAX_RETRIES 5
     46 
     47 /**
     48  * Information we keep for an individual call to the /abort handler.
     49  */
     50 struct AbortContext;
     51 
     52 /**
     53  * Information kept during a /abort request for each coin.
     54  */
     55 struct RefundDetails
     56 {
     57 
     58   /**
     59    * Public key of the coin.
     60    */
     61   struct TALER_CoinSpendPublicKeyP coin_pub;
     62 
     63   /**
     64    * Signature from the exchange confirming the refund.
     65    * Set if we were successful (status 200).
     66    */
     67   struct TALER_ExchangeSignatureP exchange_sig;
     68 
     69   /**
     70    * Public key used for @e exchange_sig.
     71    * Set if we were successful (status 200).
     72    */
     73   struct TALER_ExchangePublicKeyP exchange_pub;
     74 
     75   /**
     76    * Reference to the main AbortContext
     77    */
     78   struct AbortContext *ac;
     79 
     80   /**
     81    * Handle to the refund operation we are performing for
     82    * this coin, NULL after the operation is done.
     83    */
     84   struct TALER_EXCHANGE_PostCoinsRefundHandle *rh;
     85 
     86   /**
     87    * URL of the exchange that issued this coin.
     88    */
     89   char *exchange_url;
     90 
     91   /**
     92    * Body of the response from the exchange.  Note that the body returned MUST
     93    * be freed (if non-NULL).
     94    */
     95   json_t *exchange_reply;
     96 
     97   /**
     98    * Amount this coin contributes to the total purchase price.
     99    * This amount includes the deposit fee.
    100    */
    101   struct TALER_Amount amount_with_fee;
    102 
    103   /**
    104    * Offset of this coin into the `rd` array of all coins in the
    105    * @e ac.
    106    */
    107   unsigned int index;
    108 
    109   /**
    110    * HTTP status returned by the exchange (if any).
    111    */
    112   unsigned int http_status;
    113 
    114   /**
    115    * Did we try to process this refund yet?
    116    */
    117   bool processed;
    118 
    119   /**
    120    * Did we find the deposit in our own database?
    121    */
    122   bool found_deposit;
    123 };
    124 
    125 
    126 /**
    127  * Information we keep for an individual call to the /abort handler.
    128  */
    129 struct AbortContext
    130 {
    131 
    132   /**
    133    * Hashed contract terms (according to client).
    134    */
    135   struct TALER_PrivateContractHashP h_contract_terms;
    136 
    137   /**
    138    * Context for our operation.
    139    */
    140   struct TMH_HandlerContext *hc;
    141 
    142   /**
    143    * Stored in a DLL.
    144    */
    145   struct AbortContext *next;
    146 
    147   /**
    148    * Stored in a DLL.
    149    */
    150   struct AbortContext *prev;
    151 
    152   /**
    153    * Array with @e coins_cnt coins we are despositing.
    154    */
    155   struct RefundDetails *rd;
    156 
    157   /**
    158    * MHD connection to return to
    159    */
    160   struct MHD_Connection *connection;
    161 
    162   /**
    163    * Task called when the (suspended) processing for
    164    * the /abort request times out.
    165    * Happens when we don't get a response from the exchange.
    166    */
    167   struct GNUNET_SCHEDULER_Task *timeout_task;
    168 
    169   /**
    170    * Response to return, NULL if we don't have one yet.
    171    */
    172   struct MHD_Response *response;
    173 
    174   /**
    175    * Handle to the exchange that we are doing the abortment with.
    176    * (initially NULL while @e fo is trying to find a exchange).
    177    */
    178   struct TALER_EXCHANGE_Handle *mh;
    179 
    180   /**
    181    * Handle for operation to lookup /keys (and auditors) from
    182    * the exchange used for this transaction; NULL if no operation is
    183    * pending.
    184    */
    185   struct TMH_EXCHANGES_KeysOperation *fo;
    186 
    187   /**
    188    * URL of the exchange used for the last @e fo.
    189    */
    190   const char *current_exchange;
    191 
    192   /**
    193    * Number of coins this abort is for.  Length of the @e rd array.
    194    */
    195   size_t coins_cnt;
    196 
    197   /**
    198    * How often have we retried the 'main' transaction?
    199    */
    200   unsigned int retry_counter;
    201 
    202   /**
    203    * Number of transactions still pending.  Initially set to
    204    * @e coins_cnt, decremented on each transaction that
    205    * successfully finished.
    206    */
    207   size_t pending;
    208 
    209   /**
    210    * Number of transactions still pending for the currently selected
    211    * exchange.  Initially set to the number of coins started at the
    212    * exchange, decremented on each transaction that successfully
    213    * finished.  Once it hits zero, we pick the next exchange.
    214    */
    215   size_t pending_at_ce;
    216 
    217   /**
    218    * HTTP status code to use for the reply, i.e 200 for "OK".
    219    * Special value UINT_MAX is used to indicate hard errors
    220    * (no reply, return #MHD_NO).
    221    */
    222   unsigned int response_code;
    223 
    224   /**
    225    * #GNUNET_NO if the @e connection was not suspended,
    226    * #GNUNET_YES if the @e connection was suspended,
    227    * #GNUNET_SYSERR if @e connection was resumed to as
    228    * part of #MH_force_ac_resume during shutdown.
    229    */
    230   int suspended;
    231 
    232 };
    233 
    234 
    235 /**
    236  * Head of active abort context DLL.
    237  */
    238 static struct AbortContext *ac_head;
    239 
    240 /**
    241  * Tail of active abort context DLL.
    242  */
    243 static struct AbortContext *ac_tail;
    244 
    245 
    246 /**
    247  * Abort all pending /deposit operations.
    248  *
    249  * @param ac abort context to abort
    250  */
    251 static void
    252 abort_refunds (struct AbortContext *ac)
    253 {
    254   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    255               "Aborting pending /deposit operations\n");
    256   for (size_t i = 0; i<ac->coins_cnt; i++)
    257   {
    258     struct RefundDetails *rdi = &ac->rd[i];
    259 
    260     if (NULL != rdi->rh)
    261     {
    262       TALER_EXCHANGE_post_coins_refund_cancel (rdi->rh);
    263       rdi->rh = NULL;
    264     }
    265   }
    266 }
    267 
    268 
    269 void
    270 TMH_force_ac_resume ()
    271 {
    272   for (struct AbortContext *ac = ac_head;
    273        NULL != ac;
    274        ac = ac->next)
    275   {
    276     abort_refunds (ac);
    277     if (NULL != ac->timeout_task)
    278     {
    279       GNUNET_SCHEDULER_cancel (ac->timeout_task);
    280       ac->timeout_task = NULL;
    281     }
    282     if (GNUNET_YES == ac->suspended)
    283     {
    284       ac->suspended = GNUNET_SYSERR;
    285       MHD_resume_connection (ac->connection);
    286     }
    287   }
    288 }
    289 
    290 
    291 /**
    292  * Resume the given abort context and send the given response.
    293  * Stores the response in the @a ac and signals MHD to resume
    294  * the connection.  Also ensures MHD runs immediately.
    295  *
    296  * @param ac abortment context
    297  * @param response_code response code to use
    298  * @param response response data to send back
    299  */
    300 static void
    301 resume_abort_with_response (struct AbortContext *ac,
    302                             unsigned int response_code,
    303                             struct MHD_Response *response)
    304 {
    305   abort_refunds (ac);
    306   ac->response_code = response_code;
    307   ac->response = response;
    308   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    309               "Resuming /abort handling as exchange interaction is done (%u)\n",
    310               response_code);
    311   if (NULL != ac->timeout_task)
    312   {
    313     GNUNET_SCHEDULER_cancel (ac->timeout_task);
    314     ac->timeout_task = NULL;
    315   }
    316   GNUNET_assert (GNUNET_YES == ac->suspended);
    317   ac->suspended = GNUNET_NO;
    318   MHD_resume_connection (ac->connection);
    319   TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
    320 }
    321 
    322 
    323 /**
    324  * Resume abortment processing with an error.
    325  *
    326  * @param ac operation to resume
    327  * @param http_status http status code to return
    328  * @param ec taler error code to return
    329  * @param msg human readable error message
    330  */
    331 static void
    332 resume_abort_with_error (struct AbortContext *ac,
    333                          unsigned int http_status,
    334                          enum TALER_ErrorCode ec,
    335                          const char *msg)
    336 {
    337   resume_abort_with_response (ac,
    338                               http_status,
    339                               TALER_MHD_make_error (ec,
    340                                                     msg));
    341 }
    342 
    343 
    344 /**
    345  * Generate a response that indicates abortment success.
    346  *
    347  * @param ac abortment context
    348  */
    349 static void
    350 generate_success_response (struct AbortContext *ac)
    351 {
    352   json_t *refunds;
    353   unsigned int hc = MHD_HTTP_OK;
    354 
    355   refunds = json_array ();
    356   if (NULL == refunds)
    357   {
    358     GNUNET_break (0);
    359     resume_abort_with_error (ac,
    360                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    361                              TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
    362                              "could not create JSON array");
    363     return;
    364   }
    365   for (size_t i = 0; i<ac->coins_cnt; i++)
    366   {
    367     struct RefundDetails *rdi = &ac->rd[i];
    368     json_t *detail;
    369 
    370     if (rdi->found_deposit)
    371     {
    372       if ( ( (MHD_HTTP_BAD_REQUEST <= rdi->http_status) &&
    373              (MHD_HTTP_NOT_FOUND != rdi->http_status) &&
    374              (MHD_HTTP_GONE != rdi->http_status) ) ||
    375            (0 == rdi->http_status) ||
    376            (NULL == rdi->exchange_reply) )
    377       {
    378         hc = MHD_HTTP_BAD_GATEWAY;
    379       }
    380     }
    381     if (! rdi->found_deposit)
    382     {
    383       detail = GNUNET_JSON_PACK (
    384         GNUNET_JSON_pack_string ("type",
    385                                  "undeposited"));
    386     }
    387     else
    388     {
    389       if (MHD_HTTP_OK != rdi->http_status)
    390       {
    391         detail = GNUNET_JSON_PACK (
    392           GNUNET_JSON_pack_string ("type",
    393                                    "failure"),
    394           GNUNET_JSON_pack_uint64 ("exchange_status",
    395                                    rdi->http_status),
    396           GNUNET_JSON_pack_uint64 ("exchange_code",
    397                                    (NULL != rdi->exchange_reply)
    398                                    ? TALER_JSON_get_error_code (
    399                                      rdi->exchange_reply)
    400                                    : TALER_EC_GENERIC_INVALID_RESPONSE),
    401           GNUNET_JSON_pack_allow_null (
    402             GNUNET_JSON_pack_object_incref ("exchange_reply",
    403                                             rdi->exchange_reply)));
    404       }
    405       else
    406       {
    407         detail = GNUNET_JSON_PACK (
    408           GNUNET_JSON_pack_string ("type",
    409                                    "success"),
    410           GNUNET_JSON_pack_uint64 ("exchange_status",
    411                                    rdi->http_status),
    412           GNUNET_JSON_pack_data_auto ("exchange_sig",
    413                                       &rdi->exchange_sig),
    414           GNUNET_JSON_pack_data_auto ("exchange_pub",
    415                                       &rdi->exchange_pub));
    416       }
    417     }
    418     GNUNET_assert (0 ==
    419                    json_array_append_new (refunds,
    420                                           detail));
    421   }
    422 
    423   /* Resume and send back the response.  */
    424   resume_abort_with_response (
    425     ac,
    426     hc,
    427     TALER_MHD_MAKE_JSON_PACK (
    428       GNUNET_JSON_pack_array_steal ("refunds",
    429                                     refunds)));
    430 }
    431 
    432 
    433 /**
    434  * Custom cleanup routine for a `struct AbortContext`.
    435  *
    436  * @param cls the `struct AbortContext` to clean up.
    437  */
    438 static void
    439 abort_context_cleanup (void *cls)
    440 {
    441   struct AbortContext *ac = cls;
    442 
    443   if (NULL != ac->timeout_task)
    444   {
    445     GNUNET_SCHEDULER_cancel (ac->timeout_task);
    446     ac->timeout_task = NULL;
    447   }
    448   abort_refunds (ac);
    449   for (size_t i = 0; i<ac->coins_cnt; i++)
    450   {
    451     struct RefundDetails *rdi = &ac->rd[i];
    452 
    453     if (NULL != rdi->exchange_reply)
    454     {
    455       json_decref (rdi->exchange_reply);
    456       rdi->exchange_reply = NULL;
    457     }
    458     GNUNET_free (rdi->exchange_url);
    459   }
    460   GNUNET_free (ac->rd);
    461   if (NULL != ac->fo)
    462   {
    463     TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
    464     ac->fo = NULL;
    465   }
    466   if (NULL != ac->response)
    467   {
    468     MHD_destroy_response (ac->response);
    469     ac->response = NULL;
    470   }
    471   GNUNET_CONTAINER_DLL_remove (ac_head,
    472                                ac_tail,
    473                                ac);
    474   GNUNET_free (ac);
    475 }
    476 
    477 
    478 /**
    479  * Find the exchange we need to talk to for the next
    480  * pending deposit permission.
    481  *
    482  * @param ac abortment context we are processing
    483  */
    484 static void
    485 find_next_exchange (struct AbortContext *ac);
    486 
    487 
    488 /**
    489  * Function called with the result from the exchange (to be
    490  * passed back to the wallet).
    491  *
    492  * @param cls closure
    493  * @param rr response data
    494  */
    495 static void
    496 refund_cb (void *cls,
    497            const struct TALER_EXCHANGE_PostCoinsRefundResponse *rr)
    498 {
    499   struct RefundDetails *rd = cls;
    500   const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
    501   struct AbortContext *ac = rd->ac;
    502 
    503   rd->rh = NULL;
    504   rd->http_status = hr->http_status;
    505   rd->exchange_reply = json_incref ((json_t*) hr->reply);
    506   if (MHD_HTTP_OK == hr->http_status)
    507   {
    508     rd->exchange_pub = rr->details.ok.exchange_pub;
    509     rd->exchange_sig = rr->details.ok.exchange_sig;
    510   }
    511   ac->pending_at_ce--;
    512   if (0 == ac->pending_at_ce)
    513     find_next_exchange (ac);
    514 }
    515 
    516 
    517 /**
    518  * Function called with the result of our exchange lookup.
    519  *
    520  * @param cls the `struct AbortContext`
    521  * @param keys keys of the exchange
    522  * @param exchange representation of the exchange
    523  */
    524 static void
    525 process_abort_with_exchange (void *cls,
    526                              struct TALER_EXCHANGE_Keys *keys,
    527                              struct TMH_Exchange *exchange)
    528 {
    529   struct AbortContext *ac = cls;
    530 
    531   (void) exchange;
    532   ac->fo = NULL;
    533   GNUNET_assert (GNUNET_YES == ac->suspended);
    534   if (NULL == keys)
    535   {
    536     resume_abort_with_response (
    537       ac,
    538       MHD_HTTP_GATEWAY_TIMEOUT,
    539       TALER_MHD_make_error (
    540         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
    541         NULL));
    542     return;
    543   }
    544   /* Initiate refund operation for all coins of
    545      the current exchange (!) */
    546   GNUNET_assert (0 == ac->pending_at_ce);
    547   for (size_t i = 0; i<ac->coins_cnt; i++)
    548   {
    549     struct RefundDetails *rdi = &ac->rd[i];
    550 
    551     if (rdi->processed)
    552       continue;
    553     GNUNET_assert (NULL == rdi->rh);
    554     if (0 != strcmp (rdi->exchange_url,
    555                      ac->current_exchange))
    556       continue;
    557     rdi->processed = true;
    558     ac->pending--;
    559     if (! rdi->found_deposit)
    560     {
    561       /* Coin wasn't even deposited yet, we do not need to refund it. */
    562       continue;
    563     }
    564     rdi->rh = TALER_EXCHANGE_post_coins_refund_create (
    565       TMH_curl_ctx,
    566       ac->current_exchange,
    567       keys,
    568       &rdi->amount_with_fee,
    569       &ac->h_contract_terms,
    570       &rdi->coin_pub,
    571       0,                                /* rtransaction_id */
    572       &ac->hc->instance->merchant_priv);
    573     if (NULL == rdi->rh)
    574     {
    575       GNUNET_break_op (0);
    576       resume_abort_with_error (ac,
    577                                MHD_HTTP_INTERNAL_SERVER_ERROR,
    578                                TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED,
    579                                "Failed to start refund with exchange");
    580       return;
    581     }
    582     GNUNET_assert (TALER_EC_NONE ==
    583                    TALER_EXCHANGE_post_coins_refund_start (rdi->rh,
    584                                                            &refund_cb,
    585                                                            rdi));
    586     ac->pending_at_ce++;
    587   }
    588   /* Still continue if no coins for this exchange were deposited. */
    589   if (0 == ac->pending_at_ce)
    590     find_next_exchange (ac);
    591 }
    592 
    593 
    594 /**
    595  * Begin of the DB transaction.  If required (from
    596  * soft/serialization errors), the transaction can be
    597  * restarted here.
    598  *
    599  * @param ac abortment context to transact
    600  */
    601 static void
    602 begin_transaction (struct AbortContext *ac);
    603 
    604 
    605 /**
    606  * Find the exchange we need to talk to for the next
    607  * pending deposit permission.
    608  *
    609  * @param ac abortment context we are processing
    610  */
    611 static void
    612 find_next_exchange (struct AbortContext *ac)
    613 {
    614   for (size_t i = 0; i<ac->coins_cnt; i++)
    615   {
    616     struct RefundDetails *rdi = &ac->rd[i];
    617 
    618     if (! rdi->processed)
    619     {
    620       ac->current_exchange = rdi->exchange_url;
    621       ac->fo = TMH_EXCHANGES_keys4exchange (ac->current_exchange,
    622                                             false,
    623                                             &process_abort_with_exchange,
    624                                             ac);
    625       if (NULL == ac->fo)
    626       {
    627         /* strange, should have happened on pay! */
    628         GNUNET_break (0);
    629         resume_abort_with_error (ac,
    630                                  MHD_HTTP_INTERNAL_SERVER_ERROR,
    631                                  TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
    632                                  ac->current_exchange);
    633         return;
    634       }
    635       return;
    636     }
    637   }
    638   ac->current_exchange = NULL;
    639   GNUNET_assert (0 == ac->pending);
    640   /* We are done with all the HTTP requests, go back and try
    641      the 'big' database transaction! (It should work now!) */
    642   begin_transaction (ac);
    643 }
    644 
    645 
    646 /**
    647  * Function called with information about a coin that was deposited.
    648  *
    649  * @param cls closure
    650  * @param exchange_url exchange where @a coin_pub was deposited
    651  * @param coin_pub public key of the coin
    652  * @param amount_with_fee amount the exchange will deposit for this coin
    653  * @param deposit_fee fee the exchange will charge for this coin
    654  * @param refund_fee fee the exchange will charge for refunding this coin
    655  */
    656 static void
    657 refund_coins (void *cls,
    658               const char *exchange_url,
    659               const struct TALER_CoinSpendPublicKeyP *coin_pub,
    660               const struct TALER_Amount *amount_with_fee,
    661               const struct TALER_Amount *deposit_fee,
    662               const struct TALER_Amount *refund_fee)
    663 {
    664   struct AbortContext *ac = cls;
    665   struct GNUNET_TIME_Timestamp now;
    666 
    667   (void) deposit_fee;
    668   (void) refund_fee;
    669   now = GNUNET_TIME_timestamp_get ();
    670   for (size_t i = 0; i<ac->coins_cnt; i++)
    671   {
    672     struct RefundDetails *rdi = &ac->rd[i];
    673     enum GNUNET_DB_QueryStatus qs;
    674 
    675     if ( (0 !=
    676           GNUNET_memcmp (coin_pub,
    677                          &rdi->coin_pub)) ||
    678          (0 !=
    679           strcmp (exchange_url,
    680                   rdi->exchange_url)) )
    681       continue; /* not in request */
    682     rdi->found_deposit = true;
    683     rdi->amount_with_fee = *amount_with_fee;
    684     /* Store refund in DB */
    685     qs = TMH_db->refund_coin (TMH_db->cls,
    686                               ac->hc->instance->settings.id,
    687                               &ac->h_contract_terms,
    688                               now,
    689                               coin_pub,
    690                               /* justification */
    691                               "incomplete abortment aborted");
    692     if (0 > qs)
    693     {
    694       TMH_db->rollback (TMH_db->cls);
    695       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    696       {
    697         begin_transaction (ac);
    698         return;
    699       }
    700       /* Always report on hard error as well to enable diagnostics */
    701       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    702       resume_abort_with_error (ac,
    703                                MHD_HTTP_INTERNAL_SERVER_ERROR,
    704                                TALER_EC_GENERIC_DB_STORE_FAILED,
    705                                "refund_coin");
    706       return;
    707     }
    708   } /* for all coins */
    709 }
    710 
    711 
    712 /**
    713  * Begin of the DB transaction.  If required (from soft/serialization errors),
    714  * the transaction can be restarted here.
    715  *
    716  * @param ac abortment context to transact
    717  */
    718 static void
    719 begin_transaction (struct AbortContext *ac)
    720 {
    721   enum GNUNET_DB_QueryStatus qs;
    722 
    723   /* Avoid re-trying transactions on soft errors forever! */
    724   if (ac->retry_counter++ > MAX_RETRIES)
    725   {
    726     GNUNET_break (0);
    727     resume_abort_with_error (ac,
    728                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    729                              TALER_EC_GENERIC_DB_SOFT_FAILURE,
    730                              NULL);
    731     return;
    732   }
    733   GNUNET_assert (GNUNET_YES == ac->suspended);
    734 
    735   /* First, try to see if we have all we need already done */
    736   TMH_db->preflight (TMH_db->cls);
    737   if (GNUNET_OK !=
    738       TMH_db->start (TMH_db->cls,
    739                      "run abort"))
    740   {
    741     GNUNET_break (0);
    742     resume_abort_with_error (ac,
    743                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    744                              TALER_EC_GENERIC_DB_START_FAILED,
    745                              NULL);
    746     return;
    747   }
    748 
    749   /* check payment was indeed incomplete
    750      (now that we are in the transaction scope!) */
    751   {
    752     struct TALER_PrivateContractHashP h_contract_terms;
    753     bool paid;
    754 
    755     qs = TMH_db->lookup_order_status (TMH_db->cls,
    756                                       ac->hc->instance->settings.id,
    757                                       ac->hc->infix,
    758                                       &h_contract_terms,
    759                                       &paid);
    760     switch (qs)
    761     {
    762     case GNUNET_DB_STATUS_SOFT_ERROR:
    763     case GNUNET_DB_STATUS_HARD_ERROR:
    764       /* Always report on hard error to enable diagnostics */
    765       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    766       TMH_db->rollback (TMH_db->cls);
    767       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    768       {
    769         begin_transaction (ac);
    770         return;
    771       }
    772       /* Always report on hard error as well to enable diagnostics */
    773       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    774       resume_abort_with_error (ac,
    775                                MHD_HTTP_INTERNAL_SERVER_ERROR,
    776                                TALER_EC_GENERIC_DB_FETCH_FAILED,
    777                                "order status");
    778       return;
    779     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    780       TMH_db->rollback (TMH_db->cls);
    781       resume_abort_with_error (ac,
    782                                MHD_HTTP_NOT_FOUND,
    783                                TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND,
    784                                "Could not find contract");
    785       return;
    786     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    787       if (paid)
    788       {
    789         /* Payment is complete, refuse to abort. */
    790         TMH_db->rollback (TMH_db->cls);
    791         resume_abort_with_error (ac,
    792                                  MHD_HTTP_PRECONDITION_FAILED,
    793                                  TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
    794                                  "Payment was complete, refusing to abort");
    795         return;
    796       }
    797     }
    798     if (0 !=
    799         GNUNET_memcmp (&ac->h_contract_terms,
    800                        &h_contract_terms))
    801     {
    802       GNUNET_break_op (0);
    803       TMH_db->rollback (TMH_db->cls);
    804       resume_abort_with_error (ac,
    805                                MHD_HTTP_FORBIDDEN,
    806                                TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH,
    807                                "Provided hash does not match order on file");
    808       return;
    809     }
    810   }
    811 
    812   /* Mark all deposits we have in our database for the order as refunded. */
    813   qs = TMH_db->lookup_deposits (TMH_db->cls,
    814                                 ac->hc->instance->settings.id,
    815                                 &ac->h_contract_terms,
    816                                 &refund_coins,
    817                                 ac);
    818   if (0 > qs)
    819   {
    820     TMH_db->rollback (TMH_db->cls);
    821     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    822     {
    823       begin_transaction (ac);
    824       return;
    825     }
    826     /* Always report on hard error as well to enable diagnostics */
    827     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    828     resume_abort_with_error (ac,
    829                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    830                              TALER_EC_GENERIC_DB_FETCH_FAILED,
    831                              "deposits");
    832     return;
    833   }
    834 
    835   qs = TMH_db->commit (TMH_db->cls);
    836   if (0 > qs)
    837   {
    838     TMH_db->rollback (TMH_db->cls);
    839     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    840     {
    841       begin_transaction (ac);
    842       return;
    843     }
    844     resume_abort_with_error (ac,
    845                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    846                              TALER_EC_GENERIC_DB_COMMIT_FAILED,
    847                              NULL);
    848     return;
    849   }
    850 
    851   /* At this point, the refund got correctly committed
    852      into the database. Tell exchange about abort/refund. */
    853   if (ac->pending > 0)
    854   {
    855     find_next_exchange (ac);
    856     return;
    857   }
    858   generate_success_response (ac);
    859 }
    860 
    861 
    862 /**
    863  * Try to parse the abort request into the given abort context.
    864  * Schedules an error response in the connection on failure.
    865  *
    866  * @param connection HTTP connection we are receiving abortment on
    867  * @param hc context we use to handle the abortment
    868  * @param ac state of the /abort call
    869  * @return #GNUNET_OK on success,
    870  *         #GNUNET_NO on failure (response was queued with MHD)
    871  *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
    872  */
    873 static enum GNUNET_GenericReturnValue
    874 parse_abort (struct MHD_Connection *connection,
    875              struct TMH_HandlerContext *hc,
    876              struct AbortContext *ac)
    877 {
    878   const json_t *coins;
    879   struct GNUNET_JSON_Specification spec[] = {
    880     GNUNET_JSON_spec_array_const ("coins",
    881                                   &coins),
    882     GNUNET_JSON_spec_fixed_auto ("h_contract",
    883                                  &ac->h_contract_terms),
    884 
    885     GNUNET_JSON_spec_end ()
    886   };
    887   enum GNUNET_GenericReturnValue res;
    888 
    889   res = TALER_MHD_parse_json_data (connection,
    890                                    hc->request_body,
    891                                    spec);
    892   if (GNUNET_YES != res)
    893   {
    894     GNUNET_break_op (0);
    895     return res;
    896   }
    897   ac->coins_cnt = json_array_size (coins);
    898   if (0 == ac->coins_cnt)
    899   {
    900     GNUNET_break_op (0);
    901     return (MHD_YES ==
    902             TALER_MHD_reply_with_error (connection,
    903                                         MHD_HTTP_BAD_REQUEST,
    904                                         TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY,
    905                                         "coins"))
    906       ? GNUNET_NO
    907       : GNUNET_SYSERR;
    908   }
    909   /* note: 1 coin = 1 deposit confirmation expected */
    910   ac->pending = ac->coins_cnt;
    911   ac->rd = GNUNET_new_array (ac->coins_cnt,
    912                              struct RefundDetails);
    913   /* This loop populates the array 'rd' in 'ac' */
    914   {
    915     unsigned int coins_index;
    916     json_t *coin;
    917     json_array_foreach (coins, coins_index, coin)
    918     {
    919       struct RefundDetails *rd = &ac->rd[coins_index];
    920       const char *exchange_url;
    921       struct GNUNET_JSON_Specification ispec[] = {
    922         TALER_JSON_spec_web_url ("exchange_url",
    923                                  &exchange_url),
    924         GNUNET_JSON_spec_fixed_auto ("coin_pub",
    925                                      &rd->coin_pub),
    926         GNUNET_JSON_spec_end ()
    927       };
    928 
    929       res = TALER_MHD_parse_json_data (connection,
    930                                        coin,
    931                                        ispec);
    932       if (GNUNET_YES != res)
    933       {
    934         GNUNET_break_op (0);
    935         return res;
    936       }
    937       rd->exchange_url = GNUNET_strdup (exchange_url);
    938       rd->index = coins_index;
    939       rd->ac = ac;
    940     }
    941   }
    942   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    943               "Handling /abort for order `%s' with contract hash `%s'\n",
    944               ac->hc->infix,
    945               GNUNET_h2s (&ac->h_contract_terms.hash));
    946   return GNUNET_OK;
    947 }
    948 
    949 
    950 /**
    951  * Handle a timeout for the processing of the abort request.
    952  *
    953  * @param cls our `struct AbortContext`
    954  */
    955 static void
    956 handle_abort_timeout (void *cls)
    957 {
    958   struct AbortContext *ac = cls;
    959 
    960   ac->timeout_task = NULL;
    961   GNUNET_assert (GNUNET_YES == ac->suspended);
    962   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    963               "Resuming abort with error after timeout\n");
    964   if (NULL != ac->fo)
    965   {
    966     TMH_EXCHANGES_keys4exchange_cancel (ac->fo);
    967     ac->fo = NULL;
    968   }
    969   resume_abort_with_error (ac,
    970                            MHD_HTTP_GATEWAY_TIMEOUT,
    971                            TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
    972                            NULL);
    973 }
    974 
    975 
    976 MHD_RESULT
    977 TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
    978                           struct MHD_Connection *connection,
    979                           struct TMH_HandlerContext *hc)
    980 {
    981   struct AbortContext *ac = hc->ctx;
    982 
    983   if (NULL == ac)
    984   {
    985     ac = GNUNET_new (struct AbortContext);
    986     GNUNET_CONTAINER_DLL_insert (ac_head,
    987                                  ac_tail,
    988                                  ac);
    989     ac->connection = connection;
    990     ac->hc = hc;
    991     hc->ctx = ac;
    992     hc->cc = &abort_context_cleanup;
    993   }
    994   if (GNUNET_SYSERR == ac->suspended)
    995     return MHD_NO; /* during shutdown, we don't generate any more replies */
    996   if (0 != ac->response_code)
    997   {
    998     MHD_RESULT res;
    999 
   1000     /* We are *done* processing the request,
   1001        just queue the response (!) */
   1002     if (UINT_MAX == ac->response_code)
   1003     {
   1004       GNUNET_break (0);
   1005       return MHD_NO; /* hard error */
   1006     }
   1007     res = MHD_queue_response (connection,
   1008                               ac->response_code,
   1009                               ac->response);
   1010     MHD_destroy_response (ac->response);
   1011     ac->response = NULL;
   1012     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1013                 "Queueing response (%u) for /abort (%s).\n",
   1014                 (unsigned int) ac->response_code,
   1015                 res ? "OK" : "FAILED");
   1016     return res;
   1017   }
   1018   {
   1019     enum GNUNET_GenericReturnValue ret;
   1020 
   1021     ret = parse_abort (connection,
   1022                        hc,
   1023                        ac);
   1024     if (GNUNET_OK != ret)
   1025       return (GNUNET_NO == ret)
   1026              ? MHD_YES
   1027              : MHD_NO;
   1028   }
   1029 
   1030   /* Abort not finished, suspend while we interact with the exchange */
   1031   GNUNET_assert (GNUNET_NO == ac->suspended);
   1032   MHD_suspend_connection (connection);
   1033   ac->suspended = GNUNET_YES;
   1034   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
   1035               "Suspending abort handling while working with the exchange\n");
   1036   ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_GENERIC_TIMEOUT,
   1037                                                    &handle_abort_timeout,
   1038                                                    ac);
   1039   begin_transaction (ac);
   1040   return MHD_YES;
   1041 }
   1042 
   1043 
   1044 /* end of taler-merchant-httpd_post-orders-ORDER_ID-abort.c */