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-purses-PURSE_PUB-merge.c (23189B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-2024 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-purses-PURSE_PUB-merge.c
     18  * @brief Handle /purses/$PID/merge requests; parses the POST and JSON and
     19  *        verifies the reserve signature before handing things off
     20  *        to the database.
     21  * @author Christian Grothoff
     22  */
     23 #include "taler/platform.h"
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h>
     28 #include <pthread.h>
     29 #include "taler/taler_dbevents.h"
     30 #include "taler/taler_json_lib.h"
     31 #include "taler/taler_kyclogic_lib.h"
     32 #include "taler/taler_mhd_lib.h"
     33 #include "taler-exchange-httpd_common_kyc.h"
     34 #include "taler-exchange-httpd_post-purses-PURSE_PUB-merge.h"
     35 #include "taler-exchange-httpd_responses.h"
     36 #include "taler/taler_exchangedb_lib.h"
     37 #include "taler-exchange-httpd_get-keys.h"
     38 
     39 
     40 /**
     41  * Closure for #merge_transaction.
     42  */
     43 struct PurseMergeContext
     44 {
     45 
     46   /**
     47    * Kept in a DLL.
     48    */
     49   struct PurseMergeContext *next;
     50 
     51   /**
     52    * Kept in a DLL.
     53    */
     54   struct PurseMergeContext *prev;
     55 
     56   /**
     57    * Our request.
     58    */
     59   struct TEH_RequestContext *rc;
     60 
     61   /**
     62    * Handle for the legitimization check.
     63    */
     64   struct TEH_LegitimizationCheckHandle *lch;
     65 
     66   /**
     67    * Fees that apply to this operation.
     68    */
     69   const struct TALER_WireFeeSet *wf;
     70 
     71   /**
     72    * Base URL of the exchange provider hosting the reserve.
     73    */
     74   char *provider_url;
     75 
     76   /**
     77    * URI of the account the purse is to be merged into.
     78    * Must be of the form 'payto://taler-reserve/$EXCHANGE_URL/RESERVE_PUB'.
     79    */
     80   struct TALER_NormalizedPayto payto_uri;
     81 
     82   /**
     83    * Response to return, if set.
     84    */
     85   struct MHD_Response *response;
     86 
     87   /**
     88    * Public key of the purse we are creating.
     89    */
     90   struct TALER_PurseContractPublicKeyP purse_pub;
     91 
     92   /**
     93    * Total amount to be put into the purse.
     94    */
     95   struct TALER_Amount target_amount;
     96 
     97   /**
     98    * Current amount in the purse.
     99    */
    100   struct TALER_Amount balance;
    101 
    102   /**
    103    * When should the purse expire.
    104    */
    105   struct GNUNET_TIME_Timestamp purse_expiration;
    106 
    107   /**
    108    * When the client signed the merge.
    109    */
    110   struct GNUNET_TIME_Timestamp merge_timestamp;
    111 
    112   /**
    113    * Our current time.
    114    */
    115   struct GNUNET_TIME_Timestamp exchange_timestamp;
    116 
    117   /**
    118    * Merge key for the purse.
    119    */
    120   struct TALER_PurseMergePublicKeyP merge_pub;
    121 
    122   /**
    123    * Signature of the reservce affiming this request.
    124    */
    125   struct TALER_ReserveSignatureP reserve_sig;
    126 
    127   /**
    128    * Signature of the client affiming the merge.
    129    */
    130   struct TALER_PurseMergeSignatureP merge_sig;
    131 
    132   /**
    133    * Public key of the reserve (account), as extracted from @e payto_uri.
    134    */
    135   union TALER_AccountPublicKeyP account_pub;
    136 
    137   /**
    138    * Hash of the contract terms of the purse.
    139    */
    140   struct TALER_PrivateContractHashP h_contract_terms;
    141 
    142   /**
    143    * Hash of the @e payto_uri.
    144    */
    145   struct TALER_NormalizedPaytoHashP h_payto;
    146 
    147   /**
    148    * KYC status of the operation.
    149    */
    150   struct TALER_EXCHANGEDB_KycStatus kyc;
    151 
    152   /**
    153    * HTTP status to return with @e response, or 0.
    154    */
    155   unsigned int http_status;
    156 
    157   /**
    158    * Minimum age for deposits into this purse.
    159    */
    160   uint32_t min_age;
    161 
    162   /**
    163    * Set to true if this request was suspended.
    164    */
    165   bool suspended;
    166 };
    167 
    168 
    169 /**
    170  * Kept in a DLL.
    171  */
    172 static struct PurseMergeContext *pmc_head;
    173 
    174 /**
    175  * Kept in a DLL.
    176  */
    177 static struct PurseMergeContext *pmc_tail;
    178 
    179 
    180 void
    181 TEH_purses_merge_cleanup ()
    182 {
    183   struct PurseMergeContext *pmc;
    184 
    185   while (NULL != (pmc = pmc_head))
    186   {
    187     GNUNET_CONTAINER_DLL_remove (pmc_head,
    188                                  pmc_tail,
    189                                  pmc);
    190     MHD_resume_connection (pmc->rc->connection);
    191   }
    192 }
    193 
    194 
    195 /**
    196  * Function called with the result of a legitimization
    197  * check.
    198  *
    199  * @param cls closure
    200  * @param lcr legitimization check result
    201  */
    202 static void
    203 legi_result_cb (
    204   void *cls,
    205   const struct TEH_LegitimizationCheckResult *lcr)
    206 {
    207   struct PurseMergeContext *pmc = cls;
    208 
    209   pmc->lch = NULL;
    210   MHD_resume_connection (pmc->rc->connection);
    211   GNUNET_CONTAINER_DLL_remove (pmc_head,
    212                                pmc_tail,
    213                                pmc);
    214   TALER_MHD_daemon_trigger ();
    215   if (NULL != lcr->response)
    216   {
    217     pmc->response = lcr->response;
    218     pmc->http_status = lcr->http_status;
    219     return;
    220   }
    221   pmc->kyc = lcr->kyc;
    222 }
    223 
    224 
    225 /**
    226  * Send confirmation of purse creation success to client.
    227  *
    228  * @param pmc details about the request that succeeded
    229  * @return MHD result code
    230  */
    231 static MHD_RESULT
    232 reply_merge_success (const struct PurseMergeContext *pmc)
    233 {
    234   struct MHD_Connection *connection = pmc->rc->connection;
    235   struct TALER_ExchangePublicKeyP pub;
    236   struct TALER_ExchangeSignatureP sig;
    237   enum TALER_ErrorCode ec;
    238   struct TALER_Amount merge_amount;
    239 
    240   if (0 <
    241       TALER_amount_cmp (&pmc->balance,
    242                         &pmc->target_amount))
    243   {
    244     GNUNET_break (0);
    245     return TALER_MHD_REPLY_JSON_PACK (
    246       connection,
    247       MHD_HTTP_INTERNAL_SERVER_ERROR,
    248       TALER_JSON_pack_amount ("balance",
    249                               &pmc->balance),
    250       TALER_JSON_pack_amount ("target_amount",
    251                               &pmc->target_amount));
    252   }
    253   if ( (NULL == pmc->provider_url) ||
    254        (0 == strcmp (pmc->provider_url,
    255                      TEH_base_url)) )
    256   {
    257     /* wad fee is always zero if we stay at our own exchange */
    258     merge_amount = pmc->target_amount;
    259   }
    260   else
    261   {
    262 #if WAD_NOT_IMPLEMENTED /* #7271 */
    263     /* FIXME: figure out partner, lookup wad fee by partner! #7271 */
    264     if (0 >
    265         TALER_amount_subtract (&merge_amount,
    266                                &pmc->target_amount,
    267                                &wad_fee))
    268     {
    269       GNUNET_assert (GNUNET_OK ==
    270                      TALER_amount_set_zero (TEH_currency,
    271                                             &merge_amount));
    272     }
    273 #else
    274     merge_amount = pmc->target_amount;
    275 #endif
    276   }
    277   if (TALER_EC_NONE !=
    278       (ec = TALER_exchange_online_purse_merged_sign (
    279          &TEH_keys_exchange_sign_,
    280          pmc->exchange_timestamp,
    281          pmc->purse_expiration,
    282          &merge_amount,
    283          &pmc->purse_pub,
    284          &pmc->h_contract_terms,
    285          &pmc->account_pub.reserve_pub,
    286          (NULL != pmc->provider_url)
    287          ? pmc->provider_url
    288          : TEH_base_url,
    289          &pub,
    290          &sig)))
    291   {
    292     GNUNET_break (0);
    293     return TALER_MHD_reply_with_ec (connection,
    294                                     ec,
    295                                     NULL);
    296   }
    297   return TALER_MHD_REPLY_JSON_PACK (
    298     connection,
    299     MHD_HTTP_OK,
    300     TALER_JSON_pack_amount ("merge_amount",
    301                             &merge_amount),
    302     GNUNET_JSON_pack_timestamp ("exchange_timestamp",
    303                                 pmc->exchange_timestamp),
    304     GNUNET_JSON_pack_data_auto ("exchange_sig",
    305                                 &sig),
    306     GNUNET_JSON_pack_data_auto ("exchange_pub",
    307                                 &pub));
    308 }
    309 
    310 
    311 /**
    312  * Function called to iterate over KYC-relevant
    313  * transaction amounts for a particular time range.
    314  * Called within a database transaction, so must
    315  * not start a new one.
    316  *
    317  * @param cls a `struct PurseMergeContext`
    318  * @param limit maximum time-range for which events
    319  *        should be fetched (timestamp in the past)
    320  * @param cb function to call on each event found,
    321  *        events must be returned in reverse chronological
    322  *        order
    323  * @param cb_cls closure for @a cb
    324  * @return transaction status
    325  */
    326 static enum GNUNET_DB_QueryStatus
    327 amount_iterator (void *cls,
    328                  struct GNUNET_TIME_Absolute limit,
    329                  TALER_EXCHANGEDB_KycAmountCallback cb,
    330                  void *cb_cls)
    331 {
    332   struct PurseMergeContext *pmc = cls;
    333   enum GNUNET_GenericReturnValue ret;
    334   enum GNUNET_DB_QueryStatus qs;
    335 
    336   ret = cb (cb_cls,
    337             &pmc->target_amount,
    338             GNUNET_TIME_absolute_get ());
    339   GNUNET_break (GNUNET_SYSERR != ret);
    340   if (GNUNET_OK != ret)
    341     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    342   qs = TEH_plugin->select_merge_amounts_for_kyc_check (
    343     TEH_plugin->cls,
    344     &pmc->h_payto,
    345     limit,
    346     cb,
    347     cb_cls);
    348   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    349               "Got %d additional transactions for this merge and limit %llu\n",
    350               qs,
    351               (unsigned long long) limit.abs_value_us);
    352   GNUNET_break (qs >= 0);
    353   return qs;
    354 }
    355 
    356 
    357 /**
    358  * Execute database transaction for /purses/$PID/merge.  Runs the transaction
    359  * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
    360  * a MHD response.  IF it returns an hard error, the transaction logic MUST
    361  * queue a MHD response and set @a mhd_ret.  IF it returns the soft error
    362  * code, the function MAY be called again to retry and MUST not queue a MHD
    363  * response.
    364  *
    365  * @param cls a `struct PurseMergeContext`
    366  * @param connection MHD request context
    367  * @param[out] mhd_ret set to MHD status on error
    368  * @return transaction status
    369  */
    370 static enum GNUNET_DB_QueryStatus
    371 merge_transaction (void *cls,
    372                    struct MHD_Connection *connection,
    373                    MHD_RESULT *mhd_ret)
    374 {
    375   struct PurseMergeContext *pmc = cls;
    376   enum GNUNET_DB_QueryStatus qs;
    377   bool in_conflict = true;
    378   bool no_balance = true;
    379   bool no_partner = true;
    380 
    381   qs = TEH_plugin->do_purse_merge (
    382     TEH_plugin->cls,
    383     &pmc->purse_pub,
    384     &pmc->merge_sig,
    385     pmc->merge_timestamp,
    386     &pmc->reserve_sig,
    387     pmc->provider_url,
    388     &pmc->account_pub.reserve_pub,
    389     &no_partner,
    390     &no_balance,
    391     &in_conflict);
    392   if (qs < 0)
    393   {
    394     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    395       return qs;
    396     GNUNET_break (0);
    397     *mhd_ret =
    398       TALER_MHD_reply_with_error (connection,
    399                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    400                                   TALER_EC_GENERIC_DB_STORE_FAILED,
    401                                   "purse merge");
    402     return qs;
    403   }
    404   if (no_partner)
    405   {
    406     *mhd_ret =
    407       TALER_MHD_reply_with_error (connection,
    408                                   MHD_HTTP_NOT_FOUND,
    409                                   TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN,
    410                                   pmc->provider_url);
    411     return GNUNET_DB_STATUS_HARD_ERROR;
    412   }
    413   if (no_balance)
    414   {
    415     *mhd_ret =
    416       TALER_MHD_reply_with_error (connection,
    417                                   MHD_HTTP_PAYMENT_REQUIRED,
    418                                   TALER_EC_EXCHANGE_PURSE_NOT_FULL,
    419                                   NULL);
    420     return GNUNET_DB_STATUS_HARD_ERROR;
    421   }
    422   if (in_conflict)
    423   {
    424     struct TALER_PurseMergeSignatureP merge_sig;
    425     struct GNUNET_TIME_Timestamp merge_timestamp;
    426     char *partner_url = NULL;
    427     struct TALER_ReservePublicKeyP reserve_pub;
    428     bool refunded;
    429 
    430     qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
    431                                          &pmc->purse_pub,
    432                                          &merge_sig,
    433                                          &merge_timestamp,
    434                                          &partner_url,
    435                                          &reserve_pub,
    436                                          &refunded);
    437     if (qs <= 0)
    438     {
    439       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    440         return qs;
    441       TALER_LOG_WARNING (
    442         "Failed to fetch merge purse information from database\n");
    443       *mhd_ret =
    444         TALER_MHD_reply_with_error (connection,
    445                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
    446                                     TALER_EC_GENERIC_DB_FETCH_FAILED,
    447                                     "select purse merge");
    448       return qs;
    449     }
    450     if (refunded)
    451     {
    452       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    453                   "Purse was already refunded\n");
    454       *mhd_ret = TALER_MHD_reply_with_error (connection,
    455                                              MHD_HTTP_GONE,
    456                                              TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
    457                                              NULL);
    458       GNUNET_free (partner_url);
    459       return GNUNET_DB_STATUS_HARD_ERROR;
    460     }
    461     if (0 !=
    462         GNUNET_memcmp (&merge_sig,
    463                        &pmc->merge_sig))
    464     {
    465       *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
    466         connection,
    467         MHD_HTTP_CONFLICT,
    468         GNUNET_JSON_pack_timestamp ("merge_timestamp",
    469                                     merge_timestamp),
    470         GNUNET_JSON_pack_data_auto ("merge_sig",
    471                                     &merge_sig),
    472         GNUNET_JSON_pack_allow_null (
    473           GNUNET_JSON_pack_string ("partner_url",
    474                                    partner_url)),
    475         GNUNET_JSON_pack_data_auto ("reserve_pub",
    476                                     &reserve_pub));
    477       GNUNET_free (partner_url);
    478       return GNUNET_DB_STATUS_HARD_ERROR;
    479     }
    480     /* idempotent! */
    481     *mhd_ret = reply_merge_success (pmc);
    482     GNUNET_free (partner_url);
    483     return GNUNET_DB_STATUS_HARD_ERROR;
    484   }
    485 
    486   return qs;
    487 }
    488 
    489 
    490 /**
    491  * Purse-merge-specific cleanup routine. Function called
    492  * upon completion of the request that should
    493  * clean up @a rh_ctx. Can be NULL.
    494  *
    495  * @param rc request context to clean up
    496  */
    497 static void
    498 clean_purse_merge_rc (struct TEH_RequestContext *rc)
    499 {
    500   struct PurseMergeContext *pmc = rc->rh_ctx;
    501 
    502   if (NULL != pmc->lch)
    503   {
    504     TEH_legitimization_check_cancel (pmc->lch);
    505     pmc->lch = NULL;
    506   }
    507   GNUNET_free (pmc->provider_url);
    508   GNUNET_free (pmc);
    509 }
    510 
    511 
    512 MHD_RESULT
    513 TEH_handler_purses_merge (
    514   struct TEH_RequestContext *rc,
    515   const struct TALER_PurseContractPublicKeyP *purse_pub,
    516   const json_t *root)
    517 {
    518   struct PurseMergeContext *pmc = rc->rh_ctx;
    519 
    520   if (NULL == pmc)
    521   {
    522     pmc = GNUNET_new (struct PurseMergeContext);
    523     rc->rh_ctx = pmc;
    524     rc->rh_cleaner = &clean_purse_merge_rc;
    525     pmc->rc = rc;
    526     pmc->purse_pub = *purse_pub;
    527     pmc->exchange_timestamp
    528       = GNUNET_TIME_timestamp_get ();
    529 
    530     {
    531       struct GNUNET_JSON_Specification spec[] = {
    532         TALER_JSON_spec_normalized_payto_uri ("payto_uri",
    533                                               &pmc->payto_uri),
    534         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    535                                      &pmc->reserve_sig),
    536         GNUNET_JSON_spec_fixed_auto ("merge_sig",
    537                                      &pmc->merge_sig),
    538         GNUNET_JSON_spec_timestamp ("merge_timestamp",
    539                                     &pmc->merge_timestamp),
    540         GNUNET_JSON_spec_end ()
    541       };
    542       enum GNUNET_GenericReturnValue res;
    543 
    544       res = TALER_MHD_parse_json_data (rc->connection,
    545                                        root,
    546                                        spec);
    547       if (GNUNET_SYSERR == res)
    548       {
    549         GNUNET_break (0);
    550         return MHD_NO; /* hard failure */
    551       }
    552       if (GNUNET_NO == res)
    553       {
    554         GNUNET_break_op (0);
    555         return MHD_YES; /* failure */
    556       }
    557     }
    558 
    559     {
    560       struct TALER_PurseContractSignatureP purse_sig;
    561       enum GNUNET_DB_QueryStatus qs;
    562 
    563       /* Fetch purse details */
    564       qs = TEH_plugin->get_purse_request (
    565         TEH_plugin->cls,
    566         &pmc->purse_pub,
    567         &pmc->merge_pub,
    568         &pmc->purse_expiration,
    569         &pmc->h_contract_terms,
    570         &pmc->min_age,
    571         &pmc->target_amount,
    572         &pmc->balance,
    573         &purse_sig);
    574       switch (qs)
    575       {
    576       case GNUNET_DB_STATUS_HARD_ERROR:
    577         GNUNET_break (0);
    578         return TALER_MHD_reply_with_error (
    579           rc->connection,
    580           MHD_HTTP_INTERNAL_SERVER_ERROR,
    581           TALER_EC_GENERIC_DB_FETCH_FAILED,
    582           "select purse request");
    583       case GNUNET_DB_STATUS_SOFT_ERROR:
    584         GNUNET_break (0);
    585         return TALER_MHD_reply_with_error (
    586           rc->connection,
    587           MHD_HTTP_INTERNAL_SERVER_ERROR,
    588           TALER_EC_GENERIC_DB_FETCH_FAILED,
    589           "select purse request");
    590       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    591         return TALER_MHD_reply_with_error (
    592           rc->connection,
    593           MHD_HTTP_NOT_FOUND,
    594           TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
    595           NULL);
    596       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    597         /* continued below */
    598         break;
    599       }
    600     }
    601 
    602     /* check signatures */
    603     if (GNUNET_OK !=
    604         TALER_wallet_purse_merge_verify (
    605           pmc->payto_uri,
    606           pmc->merge_timestamp,
    607           &pmc->purse_pub,
    608           &pmc->merge_pub,
    609           &pmc->merge_sig))
    610     {
    611       GNUNET_break_op (0);
    612       return TALER_MHD_reply_with_error (
    613         rc->connection,
    614         MHD_HTTP_FORBIDDEN,
    615         TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
    616         NULL);
    617     }
    618 
    619     /* parse 'payto_uri' into pmc->account_pub and provider_url */
    620     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    621                 "Received payto: `%s'\n",
    622                 pmc->payto_uri.normalized_payto);
    623     if ( (0 != strncmp (pmc->payto_uri.normalized_payto,
    624                         "payto://taler-reserve/",
    625                         strlen ("payto://taler-reserve/"))) &&
    626          (0 != strncmp (pmc->payto_uri.normalized_payto,
    627                         "payto://taler-reserve-http/",
    628                         strlen ("payto://taler-reserve-http/"))) )
    629     {
    630       GNUNET_break_op (0);
    631       return TALER_MHD_reply_with_error (
    632         rc->connection,
    633         MHD_HTTP_BAD_REQUEST,
    634         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    635         "payto_uri");
    636     }
    637 
    638     {
    639       bool http;
    640       const char *host;
    641       const char *slash;
    642 
    643       http = (0 == strncmp (pmc->payto_uri.normalized_payto,
    644                             "payto://taler-reserve-http/",
    645                             strlen ("payto://taler-reserve-http/")));
    646       host = &pmc->payto_uri.normalized_payto[http
    647                             ? strlen ("payto://taler-reserve-http/")
    648                             : strlen ("payto://taler-reserve/")];
    649       slash = strchr (host,
    650                       '/');
    651       if (NULL == slash)
    652       {
    653         GNUNET_break_op (0);
    654         return TALER_MHD_reply_with_error (
    655           rc->connection,
    656           MHD_HTTP_BAD_REQUEST,
    657           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    658           "payto_uri");
    659       }
    660       GNUNET_asprintf (&pmc->provider_url,
    661                        "%s://%.*s/",
    662                        http ? "http" : "https",
    663                        (int) (slash - host),
    664                        host);
    665       slash++;
    666       if (GNUNET_OK !=
    667           GNUNET_STRINGS_string_to_data (
    668             slash,
    669             strlen (slash),
    670             &pmc->account_pub.reserve_pub,
    671             sizeof (pmc->account_pub.reserve_pub)))
    672       {
    673         GNUNET_break_op (0);
    674         return TALER_MHD_reply_with_error (
    675           rc->connection,
    676           MHD_HTTP_BAD_REQUEST,
    677           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    678           "payto_uri");
    679       }
    680     }
    681     TALER_normalized_payto_hash (pmc->payto_uri,
    682                                  &pmc->h_payto);
    683     if (0 == strcmp (pmc->provider_url,
    684                      TEH_base_url))
    685     {
    686       /* we use NULL to represent 'self' as the provider */
    687       GNUNET_free (pmc->provider_url);
    688     }
    689     else
    690     {
    691       char *method = GNUNET_strdup ("FIXME-WAD #7271");
    692 
    693       /* FIXME-#7271: lookup wire method by pmc.provider_url! */
    694       pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp,
    695                                        method);
    696       if (NULL == pmc->wf)
    697       {
    698         MHD_RESULT res;
    699 
    700         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    701                     "Cannot merge purse: wire fees not configured!\n");
    702         res = TALER_MHD_reply_with_error (
    703           rc->connection,
    704           MHD_HTTP_INTERNAL_SERVER_ERROR,
    705           TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
    706           method);
    707         GNUNET_free (method);
    708         return res;
    709       }
    710       GNUNET_free (method);
    711     }
    712 
    713     {
    714       struct TALER_Amount zero_purse_fee;
    715 
    716       GNUNET_assert (GNUNET_OK ==
    717                      TALER_amount_set_zero (
    718                        pmc->target_amount.currency,
    719                        &zero_purse_fee));
    720       if (GNUNET_OK !=
    721           TALER_wallet_account_merge_verify (
    722             pmc->merge_timestamp,
    723             &pmc->purse_pub,
    724             pmc->purse_expiration,
    725             &pmc->h_contract_terms,
    726             &pmc->target_amount,
    727             &zero_purse_fee,
    728             pmc->min_age,
    729             TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
    730             &pmc->account_pub.reserve_pub,
    731             &pmc->reserve_sig))
    732       {
    733         GNUNET_break_op (0);
    734         return TALER_MHD_reply_with_error (
    735           rc->connection,
    736           MHD_HTTP_FORBIDDEN,
    737           TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
    738           NULL);
    739       }
    740     }
    741     {
    742       struct TALER_FullPayto fake_full_payto;
    743 
    744       GNUNET_asprintf (&fake_full_payto.full_payto,
    745                        "%s?receiver-name=wallet",
    746                        pmc->payto_uri.normalized_payto);
    747       pmc->lch = TEH_legitimization_check (
    748         &rc->async_scope_id,
    749         TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
    750         fake_full_payto,
    751         &pmc->h_payto,
    752         &pmc->account_pub,
    753         &amount_iterator,
    754         pmc,
    755         &legi_result_cb,
    756         pmc);
    757       GNUNET_free (fake_full_payto.full_payto);
    758     }
    759     GNUNET_assert (NULL != pmc->lch);
    760     MHD_suspend_connection (rc->connection);
    761     GNUNET_CONTAINER_DLL_insert (pmc_head,
    762                                  pmc_tail,
    763                                  pmc);
    764     return MHD_YES;
    765   }
    766   if (NULL != pmc->response)
    767   {
    768     return MHD_queue_response (rc->connection,
    769                                pmc->http_status,
    770                                pmc->response);
    771   }
    772   if (! pmc->kyc.ok)
    773     return TEH_RESPONSE_reply_kyc_required (
    774       rc->connection,
    775       &pmc->h_payto,
    776       &pmc->kyc,
    777       false);
    778 
    779   /* execute merge transaction */
    780   {
    781     MHD_RESULT mhd_ret;
    782 
    783     if (GNUNET_OK !=
    784         TEH_DB_run_transaction (rc->connection,
    785                                 "execute purse merge",
    786                                 TEH_MT_REQUEST_PURSE_MERGE,
    787                                 &mhd_ret,
    788                                 &merge_transaction,
    789                                 pmc))
    790     {
    791       return mhd_ret;
    792     }
    793   }
    794 
    795   {
    796     struct TALER_PurseEventP rep = {
    797       .header.size = htons (sizeof (rep)),
    798       .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
    799       .purse_pub = pmc->purse_pub
    800     };
    801 
    802     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    803                 "Notifying about purse merge\n");
    804     TEH_plugin->event_notify (TEH_plugin->cls,
    805                               &rep.header,
    806                               NULL,
    807                               0);
    808   }
    809 
    810   /* generate regular response */
    811   return reply_merge_success (pmc);
    812 }
    813 
    814 
    815 /* end of taler-exchange-httpd_purses_merge.c */