merchant

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

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


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