merchant

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

taler-merchant-httpd_post-orders-ID-abort.c (29362B)


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