merchant

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

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


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