merchant

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

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


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