merchant

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

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


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