exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_post-coins-COIN_PUB-refund.c (11910B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2020 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-httpd_post-coins-COIN_PUB-refund.c
     18  * @brief Handle refund requests; parses the POST and JSON and
     19  *        verifies the coin signature before handing things off
     20  *        to the database.
     21  * @author Florian Dold
     22  * @author Benedikt Mueller
     23  * @author Christian Grothoff
     24  */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_json_lib.h>
     27 #include <jansson.h>
     28 #include <microhttpd.h>
     29 #include <pthread.h>
     30 #include "taler/taler_json_lib.h"
     31 #include "taler/taler_mhd_lib.h"
     32 #include "taler-exchange-httpd_post-coins-COIN_PUB-refund.h"
     33 #include "taler-exchange-httpd_responses.h"
     34 #include "taler-exchange-httpd_get-keys.h"
     35 #include "exchange-database/get_coin_denomination.h"
     36 #include "exchange-database/do_refund.h"
     37 
     38 
     39 /**
     40  * How often do we retry after soft database errors?
     41  */
     42 #define MAX_RETRIES 3
     43 
     44 
     45 /**
     46  * Generate successful refund confirmation message.
     47  *
     48  * @param connection connection to the client
     49  * @param coin_pub public key of the coin
     50  * @param refund details about the successful refund
     51  * @return MHD result code
     52  */
     53 static enum MHD_Result
     54 reply_refund_success (struct MHD_Connection *connection,
     55                       const struct TALER_CoinSpendPublicKeyP *coin_pub,
     56                       const struct TALER_EXCHANGEDB_RefundListEntry *refund)
     57 {
     58   struct TALER_ExchangePublicKeyP pub;
     59   struct TALER_ExchangeSignatureP sig;
     60   enum TALER_ErrorCode ec;
     61 
     62   if (TALER_EC_NONE !=
     63       (ec = TALER_exchange_online_refund_confirmation_sign (
     64          &TEH_keys_exchange_sign_,
     65          &refund->h_contract_terms,
     66          coin_pub,
     67          &refund->merchant_pub,
     68          refund->rtransaction_id,
     69          &refund->refund_amount,
     70          &pub,
     71          &sig)))
     72   {
     73     return TALER_MHD_reply_with_ec (connection,
     74                                     ec,
     75                                     NULL);
     76   }
     77   return TALER_MHD_REPLY_JSON_PACK (
     78     connection,
     79     MHD_HTTP_OK,
     80     GNUNET_JSON_pack_data_auto ("exchange_sig",
     81                                 &sig),
     82     GNUNET_JSON_pack_data_auto ("exchange_pub",
     83                                 &pub));
     84 }
     85 
     86 
     87 /**
     88  * Closure for refund_transaction().
     89  */
     90 struct RefundContext
     91 {
     92   /**
     93    * Details about the deposit operation.
     94    */
     95   const struct TALER_EXCHANGEDB_Refund *refund;
     96 
     97   /**
     98    * Deposit fee of the coin.
     99    */
    100   struct TALER_Amount deposit_fee;
    101 
    102   /**
    103    * Unique ID of the coin in known_coins.
    104    */
    105   uint64_t known_coin_id;
    106 };
    107 
    108 
    109 /**
    110  * Execute a "/refund" transaction.  Returns a confirmation that the
    111  * refund was successful, or a failure if we are not aware of a
    112  * matching /deposit or if it is too late to do the refund.
    113  *
    114  * IF it returns a non-error code, the transaction logic MUST
    115  * NOT queue a MHD response.  IF it returns an hard error, the
    116  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
    117  * it returns the soft error code, the function MAY be called again to
    118  * retry and MUST not queue a MHD response.
    119  *
    120  * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
    121  * @param connection MHD request which triggered the transaction
    122  * @param[out] mhd_ret set to MHD response status for @a connection,
    123  *             if transaction failed (!)
    124  * @return transaction status
    125  */
    126 static enum GNUNET_DB_QueryStatus
    127 refund_transaction (void *cls,
    128                     struct MHD_Connection *connection,
    129                     enum MHD_Result *mhd_ret)
    130 {
    131   struct RefundContext *rctx = cls;
    132   const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund;
    133   enum GNUNET_DB_QueryStatus qs;
    134   bool not_found;
    135   bool refund_ok;
    136   bool conflict;
    137   bool gone;
    138 
    139   /* Finally, store new refund data */
    140   qs = TALER_EXCHANGEDB_do_refund (TEH_pg,
    141                                    refund,
    142                                    &rctx->deposit_fee,
    143                                    rctx->known_coin_id,
    144                                    &not_found,
    145                                    &refund_ok,
    146                                    &gone,
    147                                    &conflict);
    148   if (0 > qs)
    149   {
    150     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    151       *mhd_ret = TALER_MHD_reply_with_error (
    152         connection,
    153         MHD_HTTP_INTERNAL_SERVER_ERROR,
    154         TALER_EC_GENERIC_DB_STORE_FAILED,
    155         "do refund");
    156     return qs;
    157   }
    158 
    159   if (gone)
    160   {
    161     *mhd_ret = TALER_MHD_reply_with_error (
    162       connection,
    163       MHD_HTTP_GONE,
    164       TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID,
    165       NULL);
    166     return GNUNET_DB_STATUS_HARD_ERROR;
    167   }
    168   if (conflict)
    169   {
    170     GNUNET_break_op (0);
    171     *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
    172       connection,
    173       TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
    174       &refund->coin.denom_pub_hash,
    175       &refund->coin.coin_pub);
    176     return GNUNET_DB_STATUS_HARD_ERROR;
    177   }
    178   if (not_found)
    179   {
    180     *mhd_ret = TALER_MHD_reply_with_error (
    181       connection,
    182       MHD_HTTP_NOT_FOUND,
    183       TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND,
    184       NULL);
    185     return GNUNET_DB_STATUS_HARD_ERROR;
    186   }
    187   if (! refund_ok)
    188   {
    189     *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
    190       connection,
    191       TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT,
    192       &refund->coin.denom_pub_hash,
    193       &refund->coin.coin_pub);
    194     return GNUNET_DB_STATUS_HARD_ERROR;
    195   }
    196   return qs;
    197 }
    198 
    199 
    200 /**
    201  * We have parsed the JSON information about the refund, do some basic
    202  * sanity checks (especially that the signature on the coin is valid)
    203  * and then execute the refund.  Note that we need the DB to check
    204  * the fee structure, so this is not done here.
    205  *
    206  * @param connection the MHD connection to handle
    207  * @param[in,out] refund information about the refund
    208  * @return MHD result code
    209  */
    210 static enum MHD_Result
    211 verify_and_execute_refund (struct MHD_Connection *connection,
    212                            struct TALER_EXCHANGEDB_Refund *refund)
    213 {
    214   struct RefundContext rctx = {
    215     .refund = refund
    216   };
    217 
    218   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    219   if (GNUNET_OK !=
    220       TALER_merchant_refund_verify (&refund->coin.coin_pub,
    221                                     &refund->details.h_contract_terms,
    222                                     refund->details.rtransaction_id,
    223                                     &refund->details.refund_amount,
    224                                     &refund->details.merchant_pub,
    225                                     &refund->details.merchant_sig))
    226   {
    227     GNUNET_break_op (0);
    228     return TALER_MHD_reply_with_error (
    229       connection,
    230       MHD_HTTP_FORBIDDEN,
    231       TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID,
    232       NULL);
    233   }
    234 
    235   /* Fetch the coin's denomination (hash) */
    236   for (unsigned int i = 0; i < MAX_RETRIES; i++)
    237   {
    238     enum GNUNET_DB_QueryStatus qs;
    239 
    240     qs = TALER_EXCHANGEDB_get_coin_denomination (TEH_pg,
    241                                                  &refund->coin.coin_pub,
    242                                                  &rctx.known_coin_id,
    243                                                  &refund->coin.denom_pub_hash);
    244     switch (qs)
    245     {
    246     case GNUNET_DB_STATUS_SOFT_ERROR:
    247       if (i < MAX_RETRIES - 1)
    248         continue;
    249     /* otherwise: fall-through */
    250     case GNUNET_DB_STATUS_HARD_ERROR:
    251       GNUNET_break (0);
    252       return TALER_MHD_reply_with_error (
    253         connection,
    254         MHD_HTTP_INTERNAL_SERVER_ERROR,
    255         TALER_EC_GENERIC_DB_FETCH_FAILED,
    256         "get_coin_denomination");
    257     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    258       {
    259         enum MHD_Result res;
    260         char *dhs;
    261 
    262         GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    263         dhs = GNUNET_STRINGS_data_to_string_alloc (
    264           &refund->coin.denom_pub_hash,
    265           sizeof (refund->coin.denom_pub_hash));
    266         res = TALER_MHD_reply_with_error (
    267           connection,
    268           MHD_HTTP_NOT_FOUND,
    269           TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND,
    270           dhs);
    271         GNUNET_free (dhs);
    272         return res;
    273       }
    274     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    275       break;
    276     }
    277     break;
    278   } /* end for retries */
    279 
    280   {
    281     /* Obtain information about the coin's denomination! */
    282     struct TEH_DenominationKey *dk;
    283     enum MHD_Result mret;
    284 
    285     dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash,
    286                                         connection,
    287                                         &mret);
    288     if (NULL == dk)
    289     {
    290       /* DKI not found, but we do have a coin with this DK in our database;
    291          not good... */
    292       GNUNET_break (0);
    293       return mret;
    294     }
    295     refund->details.refund_fee = dk->meta.fees.refund;
    296     rctx.deposit_fee = dk->meta.fees.deposit;
    297   }
    298 
    299   /* Finally run the actual transaction logic */
    300   {
    301     enum MHD_Result mhd_ret;
    302 
    303     if (GNUNET_OK !=
    304         TEH_DB_run_transaction (connection,
    305                                 "run refund",
    306                                 TEH_MT_REQUEST_OTHER,
    307                                 &mhd_ret,
    308                                 &refund_transaction,
    309                                 &rctx))
    310     {
    311       return mhd_ret;
    312     }
    313   }
    314   return reply_refund_success (connection,
    315                                &refund->coin.coin_pub,
    316                                &refund->details);
    317 }
    318 
    319 
    320 /**
    321  * Handle a "/coins/$COIN_PUB/refund" request.  Parses the JSON, and, if
    322  * successful, passes the JSON data to #verify_and_execute_refund() to further
    323  * check the details of the operation specified.  If everything checks out,
    324  * this will ultimately lead to the refund being executed, or rejected.
    325  *
    326  * @param connection the MHD connection to handle
    327  * @param coin_pub public key of the coin
    328  * @param root uploaded JSON data
    329  * @return MHD result code
    330   */
    331 enum MHD_Result
    332 TEH_handler_refund (struct MHD_Connection *connection,
    333                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
    334                     const json_t *root)
    335 {
    336   struct TALER_EXCHANGEDB_Refund refund = {
    337     .details.refund_fee.currency = {0}                                        /* set to invalid, just to be sure */
    338   };
    339   struct GNUNET_JSON_Specification spec[] = {
    340     TALER_JSON_spec_amount ("refund_amount",
    341                             TEH_currency,
    342                             &refund.details.refund_amount),
    343     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    344                                  &refund.details.h_contract_terms),
    345     GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    346                                  &refund.details.merchant_pub),
    347     GNUNET_JSON_spec_uint64 ("rtransaction_id",
    348                              &refund.details.rtransaction_id),
    349     GNUNET_JSON_spec_fixed_auto ("merchant_sig",
    350                                  &refund.details.merchant_sig),
    351     GNUNET_JSON_spec_end ()
    352   };
    353 
    354   refund.coin.coin_pub = *coin_pub;
    355   {
    356     enum GNUNET_GenericReturnValue res;
    357 
    358     res = TALER_MHD_parse_json_data (connection,
    359                                      root,
    360                                      spec);
    361     if (GNUNET_SYSERR == res)
    362       return MHD_NO; /* hard failure */
    363     if (GNUNET_NO == res)
    364       return MHD_YES; /* failure */
    365   }
    366   {
    367     enum MHD_Result res;
    368 
    369     res = verify_and_execute_refund (connection,
    370                                      &refund);
    371     GNUNET_JSON_parse_free (spec);
    372     return res;
    373   }
    374 }
    375 
    376 
    377 /* end of taler-exchange-httpd_refund.c */