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 (23542B)


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