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


      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 enum 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 #pragma GCC diagnostic push
    259 #pragma GCC diagnostic ignored "-Wduplicated-branches"
    260   if ( (NULL == pmc->provider_url) ||
    261        (0 == strcmp (pmc->provider_url,
    262                      TEH_base_url)) )
    263   {
    264     /* wad fee is always zero if we stay at our own exchange */
    265     merge_amount = pmc->target_amount;
    266   }
    267   else
    268   {
    269 #if WAD_NOT_IMPLEMENTED /* #7271 */
    270     /* FIXME: figure out partner, lookup wad fee by partner! #7271 */
    271     if (0 >
    272         TALER_amount_subtract (&merge_amount,
    273                                &pmc->target_amount,
    274                                &wad_fee))
    275     {
    276       GNUNET_assert (GNUNET_OK ==
    277                      TALER_amount_set_zero (TEH_currency,
    278                                             &merge_amount));
    279     }
    280 #else
    281     merge_amount = pmc->target_amount;
    282 #endif
    283   }
    284 #pragma GCC diagnostic pop
    285   if (TALER_EC_NONE !=
    286       (ec = TALER_exchange_online_purse_merged_sign (
    287          &TEH_keys_exchange_sign_,
    288          pmc->exchange_timestamp,
    289          pmc->purse_expiration,
    290          &merge_amount,
    291          &pmc->purse_pub,
    292          &pmc->h_contract_terms,
    293          &pmc->account_pub.reserve_pub,
    294          (NULL != pmc->provider_url)
    295          ? pmc->provider_url
    296          : TEH_base_url,
    297          &pub,
    298          &sig)))
    299   {
    300     GNUNET_break (0);
    301     return TALER_MHD_reply_with_ec (connection,
    302                                     ec,
    303                                     NULL);
    304   }
    305   return TALER_MHD_REPLY_JSON_PACK (
    306     connection,
    307     MHD_HTTP_OK,
    308     TALER_JSON_pack_amount ("merge_amount",
    309                             &merge_amount),
    310     GNUNET_JSON_pack_timestamp ("exchange_timestamp",
    311                                 pmc->exchange_timestamp),
    312     GNUNET_JSON_pack_data_auto ("exchange_sig",
    313                                 &sig),
    314     GNUNET_JSON_pack_data_auto ("exchange_pub",
    315                                 &pub));
    316 }
    317 
    318 
    319 /**
    320  * Function called to iterate over KYC-relevant
    321  * transaction amounts for a particular time range.
    322  * Called within a database transaction, so must
    323  * not start a new one.
    324  *
    325  * @param cls a `struct PurseMergeContext`
    326  * @param limit maximum time-range for which events
    327  *        should be fetched (timestamp in the past)
    328  * @param cb function to call on each event found,
    329  *        events must be returned in reverse chronological
    330  *        order
    331  * @param cb_cls closure for @a cb
    332  * @return transaction status
    333  */
    334 static enum GNUNET_DB_QueryStatus
    335 amount_iterator (void *cls,
    336                  struct GNUNET_TIME_Absolute limit,
    337                  TALER_KYCLOGIC_KycAmountCallback cb,
    338                  void *cb_cls)
    339 {
    340   struct PurseMergeContext *pmc = cls;
    341   enum GNUNET_GenericReturnValue ret;
    342   enum GNUNET_DB_QueryStatus qs;
    343 
    344   ret = cb (cb_cls,
    345             &pmc->target_amount,
    346             GNUNET_TIME_absolute_get ());
    347   GNUNET_break (GNUNET_SYSERR != ret);
    348   if (GNUNET_OK != ret)
    349     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    350   qs = TALER_EXCHANGEDB_select_merge_amounts_for_kyc_check (
    351     TEH_pg,
    352     &pmc->h_payto,
    353     limit,
    354     cb,
    355     cb_cls);
    356   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    357               "Got %d additional transactions for this merge and limit %llu\n",
    358               qs,
    359               (unsigned long long) limit.abs_value_us);
    360   GNUNET_break (qs >= 0);
    361   return qs;
    362 }
    363 
    364 
    365 /**
    366  * Execute database transaction for /purses/$PID/merge.  Runs the transaction
    367  * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
    368  * a MHD response.  IF it returns an hard error, the transaction logic MUST
    369  * queue a MHD response and set @a mhd_ret.  IF it returns the soft error
    370  * code, the function MAY be called again to retry and MUST not queue a MHD
    371  * response.
    372  *
    373  * @param cls a `struct PurseMergeContext`
    374  * @param connection MHD request context
    375  * @param[out] mhd_ret set to MHD status on error
    376  * @return transaction status
    377  */
    378 static enum GNUNET_DB_QueryStatus
    379 merge_transaction (void *cls,
    380                    struct MHD_Connection *connection,
    381                    enum MHD_Result *mhd_ret)
    382 {
    383   struct PurseMergeContext *pmc = cls;
    384   enum GNUNET_DB_QueryStatus qs;
    385   bool in_conflict = true;
    386   bool no_balance = true;
    387   bool no_partner = true;
    388 
    389   qs = TALER_EXCHANGEDB_do_purse_merge (
    390     TEH_pg,
    391     &pmc->purse_pub,
    392     &pmc->merge_sig,
    393     pmc->merge_timestamp,
    394     &pmc->reserve_sig,
    395     pmc->provider_url,
    396     &pmc->account_pub.reserve_pub,
    397     &no_partner,
    398     &no_balance,
    399     &in_conflict);
    400   if (qs < 0)
    401   {
    402     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    403       return qs;
    404     GNUNET_break (0);
    405     *mhd_ret =
    406       TALER_MHD_reply_with_error (connection,
    407                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    408                                   TALER_EC_GENERIC_DB_STORE_FAILED,
    409                                   "purse merge");
    410     return qs;
    411   }
    412   if (no_partner)
    413   {
    414     *mhd_ret =
    415       TALER_MHD_reply_with_error (connection,
    416                                   MHD_HTTP_NOT_FOUND,
    417                                   TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN,
    418                                   pmc->provider_url);
    419     return GNUNET_DB_STATUS_HARD_ERROR;
    420   }
    421   if (no_balance)
    422   {
    423     *mhd_ret =
    424       TALER_MHD_reply_with_error (connection,
    425                                   MHD_HTTP_PAYMENT_REQUIRED,
    426                                   TALER_EC_EXCHANGE_PURSE_NOT_FULL,
    427                                   NULL);
    428     return GNUNET_DB_STATUS_HARD_ERROR;
    429   }
    430   if (in_conflict)
    431   {
    432     struct TALER_PurseMergeSignatureP merge_sig;
    433     struct GNUNET_TIME_Timestamp merge_timestamp;
    434     char *partner_url = NULL;
    435     struct TALER_ReservePublicKeyP reserve_pub;
    436     bool refunded;
    437 
    438     qs = TALER_TALER_EXCHANGEDB_select_purse_merge (TEH_pg,
    439                                                     &pmc->purse_pub,
    440                                                     &merge_sig,
    441                                                     &merge_timestamp,
    442                                                     &partner_url,
    443                                                     &reserve_pub,
    444                                                     &refunded);
    445     if (qs <= 0)
    446     {
    447       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    448         return qs;
    449       TALER_LOG_WARNING (
    450         "Failed to fetch merge purse information from database\n");
    451       *mhd_ret =
    452         TALER_MHD_reply_with_error (connection,
    453                                     MHD_HTTP_INTERNAL_SERVER_ERROR,
    454                                     TALER_EC_GENERIC_DB_FETCH_FAILED,
    455                                     "select purse merge");
    456       return qs;
    457     }
    458     if (refunded)
    459     {
    460       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    461                   "Purse was already refunded\n");
    462       *mhd_ret = TALER_MHD_reply_with_error (connection,
    463                                              MHD_HTTP_GONE,
    464                                              TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
    465                                              NULL);
    466       GNUNET_free (partner_url);
    467       return GNUNET_DB_STATUS_HARD_ERROR;
    468     }
    469     if (0 !=
    470         GNUNET_memcmp (&merge_sig,
    471                        &pmc->merge_sig))
    472     {
    473       *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
    474         connection,
    475         MHD_HTTP_CONFLICT,
    476         GNUNET_JSON_pack_timestamp ("merge_timestamp",
    477                                     merge_timestamp),
    478         GNUNET_JSON_pack_data_auto ("merge_sig",
    479                                     &merge_sig),
    480         GNUNET_JSON_pack_allow_null (
    481           GNUNET_JSON_pack_string ("partner_url",
    482                                    partner_url)),
    483         GNUNET_JSON_pack_data_auto ("reserve_pub",
    484                                     &reserve_pub));
    485       GNUNET_free (partner_url);
    486       return GNUNET_DB_STATUS_HARD_ERROR;
    487     }
    488     /* idempotent! */
    489     *mhd_ret = reply_merge_success (pmc);
    490     GNUNET_free (partner_url);
    491     return GNUNET_DB_STATUS_HARD_ERROR;
    492   }
    493 
    494   return qs;
    495 }
    496 
    497 
    498 /**
    499  * Purse-merge-specific cleanup routine. Function called
    500  * upon completion of the request that should
    501  * clean up @a rh_ctx. Can be NULL.
    502  *
    503  * @param rc request context to clean up
    504  */
    505 static void
    506 clean_purse_merge_rc (struct TEH_RequestContext *rc)
    507 {
    508   struct PurseMergeContext *pmc = rc->rh_ctx;
    509 
    510   if (NULL != pmc->lch)
    511   {
    512     TEH_legitimization_check_cancel (pmc->lch);
    513     pmc->lch = NULL;
    514   }
    515   GNUNET_free (pmc->provider_url);
    516   GNUNET_free (pmc);
    517 }
    518 
    519 
    520 enum MHD_Result
    521 TEH_handler_purses_merge (
    522   struct TEH_RequestContext *rc,
    523   const struct TALER_PurseContractPublicKeyP *purse_pub,
    524   const json_t *root)
    525 {
    526   struct PurseMergeContext *pmc = rc->rh_ctx;
    527 
    528   if (NULL == pmc)
    529   {
    530     pmc = GNUNET_new (struct PurseMergeContext);
    531     rc->rh_ctx = pmc;
    532     rc->rh_cleaner = &clean_purse_merge_rc;
    533     pmc->rc = rc;
    534     pmc->purse_pub = *purse_pub;
    535     pmc->exchange_timestamp
    536       = GNUNET_TIME_timestamp_get ();
    537 
    538     {
    539       struct GNUNET_JSON_Specification spec[] = {
    540         TALER_JSON_spec_normalized_payto_uri ("payto_uri",
    541                                               &pmc->payto_uri),
    542         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    543                                      &pmc->reserve_sig),
    544         GNUNET_JSON_spec_fixed_auto ("merge_sig",
    545                                      &pmc->merge_sig),
    546         GNUNET_JSON_spec_timestamp ("merge_timestamp",
    547                                     &pmc->merge_timestamp),
    548         GNUNET_JSON_spec_end ()
    549       };
    550       enum GNUNET_GenericReturnValue res;
    551 
    552       res = TALER_MHD_parse_json_data (rc->connection,
    553                                        root,
    554                                        spec);
    555       if (GNUNET_SYSERR == res)
    556       {
    557         GNUNET_break (0);
    558         return MHD_NO; /* hard failure */
    559       }
    560       if (GNUNET_NO == res)
    561       {
    562         GNUNET_break_op (0);
    563         return MHD_YES; /* failure */
    564       }
    565     }
    566 
    567     {
    568       struct TALER_PurseContractSignatureP purse_sig;
    569       enum GNUNET_DB_QueryStatus qs;
    570 
    571       /* Fetch purse details */
    572       qs = TALER_EXCHANGEDB_get_purse_request (
    573         TEH_pg,
    574         &pmc->purse_pub,
    575         &pmc->merge_pub,
    576         &pmc->purse_expiration,
    577         &pmc->h_contract_terms,
    578         &pmc->min_age,
    579         &pmc->target_amount,
    580         &pmc->balance,
    581         &purse_sig);
    582       switch (qs)
    583       {
    584       case GNUNET_DB_STATUS_HARD_ERROR:
    585         GNUNET_break (0);
    586         return TALER_MHD_reply_with_error (
    587           rc->connection,
    588           MHD_HTTP_INTERNAL_SERVER_ERROR,
    589           TALER_EC_GENERIC_DB_FETCH_FAILED,
    590           "select purse request");
    591       case GNUNET_DB_STATUS_SOFT_ERROR:
    592         GNUNET_break (0);
    593         return TALER_MHD_reply_with_error (
    594           rc->connection,
    595           MHD_HTTP_INTERNAL_SERVER_ERROR,
    596           TALER_EC_GENERIC_DB_FETCH_FAILED,
    597           "select purse request");
    598       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    599         return TALER_MHD_reply_with_error (
    600           rc->connection,
    601           MHD_HTTP_NOT_FOUND,
    602           TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
    603           NULL);
    604       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    605         /* continued below */
    606         break;
    607       }
    608     }
    609 
    610     /* check signatures */
    611     if (GNUNET_OK !=
    612         TALER_wallet_purse_merge_verify (
    613           pmc->payto_uri,
    614           pmc->merge_timestamp,
    615           &pmc->purse_pub,
    616           &pmc->merge_pub,
    617           &pmc->merge_sig))
    618     {
    619       GNUNET_break_op (0);
    620       return TALER_MHD_reply_with_error (
    621         rc->connection,
    622         MHD_HTTP_FORBIDDEN,
    623         TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
    624         NULL);
    625     }
    626 
    627     /* parse 'payto_uri' into pmc->account_pub and provider_url */
    628     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    629                 "Received payto: `%s'\n",
    630                 pmc->payto_uri.normalized_payto);
    631     if ( (0 != strncmp (pmc->payto_uri.normalized_payto,
    632                         "payto://taler-reserve/",
    633                         strlen ("payto://taler-reserve/"))) &&
    634          (0 != strncmp (pmc->payto_uri.normalized_payto,
    635                         "payto://taler-reserve-http/",
    636                         strlen ("payto://taler-reserve-http/"))) )
    637     {
    638       GNUNET_break_op (0);
    639       return TALER_MHD_reply_with_error (
    640         rc->connection,
    641         MHD_HTTP_BAD_REQUEST,
    642         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    643         "payto_uri");
    644     }
    645 
    646     {
    647       bool http;
    648       const char *host;
    649       const char *slash;
    650 
    651       http = (0 == strncmp (pmc->payto_uri.normalized_payto,
    652                             "payto://taler-reserve-http/",
    653                             strlen ("payto://taler-reserve-http/")));
    654       host = &pmc->payto_uri.normalized_payto[http
    655                             ? strlen ("payto://taler-reserve-http/")
    656                             : strlen ("payto://taler-reserve/")];
    657       slash = strchr (host,
    658                       '/');
    659       if (NULL == slash)
    660       {
    661         GNUNET_break_op (0);
    662         return TALER_MHD_reply_with_error (
    663           rc->connection,
    664           MHD_HTTP_BAD_REQUEST,
    665           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    666           "payto_uri");
    667       }
    668       GNUNET_asprintf (&pmc->provider_url,
    669                        "%s://%.*s/",
    670                        http ? "http" : "https",
    671                        (int) (slash - host),
    672                        host);
    673       slash++;
    674       if (GNUNET_OK !=
    675           GNUNET_STRINGS_string_to_data (
    676             slash,
    677             strlen (slash),
    678             &pmc->account_pub.reserve_pub,
    679             sizeof (pmc->account_pub.reserve_pub)))
    680       {
    681         GNUNET_break_op (0);
    682         return TALER_MHD_reply_with_error (
    683           rc->connection,
    684           MHD_HTTP_BAD_REQUEST,
    685           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    686           "payto_uri");
    687       }
    688     }
    689     TALER_normalized_payto_hash (pmc->payto_uri,
    690                                  &pmc->h_payto);
    691     if (0 == strcmp (pmc->provider_url,
    692                      TEH_base_url))
    693     {
    694       /* we use NULL to represent 'self' as the provider */
    695       GNUNET_free (pmc->provider_url);
    696     }
    697     else
    698     {
    699       char *method = GNUNET_strdup ("FIXME-WAD #7271");
    700 
    701       /* FIXME-#7271: lookup wire method by pmc.provider_url! */
    702       pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp,
    703                                        method);
    704       if (NULL == pmc->wf)
    705       {
    706         enum MHD_Result res;
    707 
    708         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    709                     "Cannot merge purse: wire fees not configured!\n");
    710         res = TALER_MHD_reply_with_error (
    711           rc->connection,
    712           MHD_HTTP_INTERNAL_SERVER_ERROR,
    713           TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
    714           method);
    715         GNUNET_free (method);
    716         return res;
    717       }
    718       GNUNET_free (method);
    719     }
    720 
    721     {
    722       struct TALER_Amount zero_purse_fee;
    723 
    724       GNUNET_assert (GNUNET_OK ==
    725                      TALER_amount_set_zero (
    726                        pmc->target_amount.currency,
    727                        &zero_purse_fee));
    728       if (GNUNET_OK !=
    729           TALER_wallet_account_merge_verify (
    730             pmc->merge_timestamp,
    731             &pmc->purse_pub,
    732             pmc->purse_expiration,
    733             &pmc->h_contract_terms,
    734             &pmc->target_amount,
    735             &zero_purse_fee,
    736             pmc->min_age,
    737             TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
    738             &pmc->account_pub.reserve_pub,
    739             &pmc->reserve_sig))
    740       {
    741         GNUNET_break_op (0);
    742         return TALER_MHD_reply_with_error (
    743           rc->connection,
    744           MHD_HTTP_FORBIDDEN,
    745           TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
    746           NULL);
    747       }
    748     }
    749     {
    750       struct TALER_FullPayto fake_full_payto;
    751 
    752       GNUNET_asprintf (&fake_full_payto.full_payto,
    753                        "%s?receiver-name=wallet",
    754                        pmc->payto_uri.normalized_payto);
    755       pmc->lch = TEH_legitimization_check (
    756         &rc->async_scope_id,
    757         TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
    758         fake_full_payto,
    759         &pmc->h_payto,
    760         &pmc->account_pub,
    761         &amount_iterator,
    762         pmc,
    763         &legi_result_cb,
    764         pmc);
    765       GNUNET_free (fake_full_payto.full_payto);
    766     }
    767     GNUNET_assert (NULL != pmc->lch);
    768     MHD_suspend_connection (rc->connection);
    769     GNUNET_CONTAINER_DLL_insert (pmc_head,
    770                                  pmc_tail,
    771                                  pmc);
    772     return MHD_YES;
    773   }
    774   if (NULL != pmc->response)
    775   {
    776     return MHD_queue_response (rc->connection,
    777                                pmc->http_status,
    778                                pmc->response);
    779   }
    780   if (! pmc->kyc.ok)
    781     return TEH_RESPONSE_reply_kyc_required (
    782       rc->connection,
    783       &pmc->h_payto,
    784       &pmc->kyc,
    785       false);
    786 
    787   /* execute merge transaction */
    788   {
    789     enum MHD_Result mhd_ret;
    790 
    791     if (GNUNET_OK !=
    792         TEH_DB_run_transaction (rc->connection,
    793                                 "execute purse merge",
    794                                 TEH_MT_REQUEST_PURSE_MERGE,
    795                                 &mhd_ret,
    796                                 &merge_transaction,
    797                                 pmc))
    798     {
    799       return mhd_ret;
    800     }
    801   }
    802 
    803   {
    804     struct TALER_EXCHANGEDB_PurseEventP rep = {
    805       .header.size = htons (sizeof (rep)),
    806       .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
    807       .purse_pub = pmc->purse_pub
    808     };
    809 
    810     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    811                 "Notifying about purse merge\n");
    812     TALER_EXCHANGEDB_event_notify (TEH_pg,
    813                                    &rep.header,
    814                                    NULL,
    815                                    0);
    816   }
    817 
    818   /* generate regular response */
    819   return reply_merge_success (pmc);
    820 }
    821 
    822 
    823 /* end of taler-exchange-httpd_purses_merge.c */