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


      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   if (NULL != pmc->response)
    516   {
    517     MHD_destroy_response (pmc->response);
    518     pmc->response = NULL;
    519   }
    520   GNUNET_free (pmc->provider_url);
    521   GNUNET_free (pmc);
    522 }
    523 
    524 
    525 enum MHD_Result
    526 TEH_handler_purses_merge (
    527   struct TEH_RequestContext *rc,
    528   const struct TALER_PurseContractPublicKeyP *purse_pub,
    529   const json_t *root)
    530 {
    531   struct PurseMergeContext *pmc = rc->rh_ctx;
    532 
    533   if (NULL == pmc)
    534   {
    535     pmc = GNUNET_new (struct PurseMergeContext);
    536     rc->rh_ctx = pmc;
    537     rc->rh_cleaner = &clean_purse_merge_rc;
    538     pmc->rc = rc;
    539     pmc->purse_pub = *purse_pub;
    540     pmc->exchange_timestamp
    541       = GNUNET_TIME_timestamp_get ();
    542 
    543     {
    544       struct GNUNET_JSON_Specification spec[] = {
    545         TALER_JSON_spec_normalized_payto_uri ("payto_uri",
    546                                               &pmc->payto_uri),
    547         GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    548                                      &pmc->reserve_sig),
    549         GNUNET_JSON_spec_fixed_auto ("merge_sig",
    550                                      &pmc->merge_sig),
    551         GNUNET_JSON_spec_timestamp ("merge_timestamp",
    552                                     &pmc->merge_timestamp),
    553         GNUNET_JSON_spec_end ()
    554       };
    555       enum GNUNET_GenericReturnValue res;
    556 
    557       res = TALER_MHD_parse_json_data (rc->connection,
    558                                        root,
    559                                        spec);
    560       if (GNUNET_SYSERR == res)
    561       {
    562         GNUNET_break (0);
    563         return MHD_NO; /* hard failure */
    564       }
    565       if (GNUNET_NO == res)
    566       {
    567         GNUNET_break_op (0);
    568         return MHD_YES; /* failure */
    569       }
    570     }
    571 
    572     {
    573       struct TALER_PurseContractSignatureP purse_sig;
    574       enum GNUNET_DB_QueryStatus qs;
    575 
    576       /* Fetch purse details */
    577       qs = TALER_EXCHANGEDB_get_purse_request (
    578         TEH_pg,
    579         &pmc->purse_pub,
    580         &pmc->merge_pub,
    581         &pmc->purse_expiration,
    582         &pmc->h_contract_terms,
    583         &pmc->min_age,
    584         &pmc->target_amount,
    585         &pmc->balance,
    586         &purse_sig);
    587       switch (qs)
    588       {
    589       case GNUNET_DB_STATUS_HARD_ERROR:
    590         GNUNET_break (0);
    591         return TALER_MHD_reply_with_error (
    592           rc->connection,
    593           MHD_HTTP_INTERNAL_SERVER_ERROR,
    594           TALER_EC_GENERIC_DB_FETCH_FAILED,
    595           "select purse request");
    596       case GNUNET_DB_STATUS_SOFT_ERROR:
    597         GNUNET_break (0);
    598         return TALER_MHD_reply_with_error (
    599           rc->connection,
    600           MHD_HTTP_INTERNAL_SERVER_ERROR,
    601           TALER_EC_GENERIC_DB_FETCH_FAILED,
    602           "select purse request");
    603       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    604         return TALER_MHD_reply_with_error (
    605           rc->connection,
    606           MHD_HTTP_NOT_FOUND,
    607           TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
    608           NULL);
    609       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    610         /* continued below */
    611         break;
    612       }
    613     }
    614 
    615     /* check signatures */
    616     if (GNUNET_OK !=
    617         TALER_wallet_purse_merge_verify (
    618           pmc->payto_uri,
    619           pmc->merge_timestamp,
    620           &pmc->purse_pub,
    621           &pmc->merge_pub,
    622           &pmc->merge_sig))
    623     {
    624       GNUNET_break_op (0);
    625       return TALER_MHD_reply_with_error (
    626         rc->connection,
    627         MHD_HTTP_FORBIDDEN,
    628         TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
    629         NULL);
    630     }
    631 
    632     /* parse 'payto_uri' into pmc->account_pub and provider_url */
    633     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    634                 "Received payto: `%s'\n",
    635                 pmc->payto_uri.normalized_payto);
    636     if ( (0 != strncmp (pmc->payto_uri.normalized_payto,
    637                         "payto://taler-reserve/",
    638                         strlen ("payto://taler-reserve/"))) &&
    639          (0 != strncmp (pmc->payto_uri.normalized_payto,
    640                         "payto://taler-reserve-http/",
    641                         strlen ("payto://taler-reserve-http/"))) )
    642     {
    643       GNUNET_break_op (0);
    644       return TALER_MHD_reply_with_error (
    645         rc->connection,
    646         MHD_HTTP_BAD_REQUEST,
    647         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    648         "payto_uri");
    649     }
    650 
    651     {
    652       bool http;
    653       const char *host;
    654       const char *slash;
    655 
    656       http = (0 == strncmp (pmc->payto_uri.normalized_payto,
    657                             "payto://taler-reserve-http/",
    658                             strlen ("payto://taler-reserve-http/")));
    659       host = &pmc->payto_uri.normalized_payto[http
    660                             ? strlen ("payto://taler-reserve-http/")
    661                             : strlen ("payto://taler-reserve/")];
    662       slash = strchr (host,
    663                       '/');
    664       if (NULL == slash)
    665       {
    666         GNUNET_break_op (0);
    667         return TALER_MHD_reply_with_error (
    668           rc->connection,
    669           MHD_HTTP_BAD_REQUEST,
    670           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    671           "payto_uri");
    672       }
    673       GNUNET_asprintf (&pmc->provider_url,
    674                        "%s://%.*s/",
    675                        http ? "http" : "https",
    676                        (int) (slash - host),
    677                        host);
    678       slash++;
    679       if (GNUNET_OK !=
    680           GNUNET_STRINGS_string_to_data (
    681             slash,
    682             strlen (slash),
    683             &pmc->account_pub.reserve_pub,
    684             sizeof (pmc->account_pub.reserve_pub)))
    685       {
    686         GNUNET_break_op (0);
    687         return TALER_MHD_reply_with_error (
    688           rc->connection,
    689           MHD_HTTP_BAD_REQUEST,
    690           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    691           "payto_uri");
    692       }
    693     }
    694     TALER_normalized_payto_hash (pmc->payto_uri,
    695                                  &pmc->h_payto);
    696     if (0 == strcmp (pmc->provider_url,
    697                      TEH_base_url))
    698     {
    699       /* we use NULL to represent 'self' as the provider */
    700       GNUNET_free (pmc->provider_url);
    701     }
    702     else
    703     {
    704       char *method = GNUNET_strdup ("FIXME-WAD #7271");
    705 
    706       /* FIXME-#7271: lookup wire method by pmc.provider_url! */
    707       pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp,
    708                                        method);
    709       if (NULL == pmc->wf)
    710       {
    711         enum MHD_Result res;
    712 
    713         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    714                     "Cannot merge purse: wire fees not configured!\n");
    715         res = TALER_MHD_reply_with_error (
    716           rc->connection,
    717           MHD_HTTP_INTERNAL_SERVER_ERROR,
    718           TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
    719           method);
    720         GNUNET_free (method);
    721         return res;
    722       }
    723       GNUNET_free (method);
    724     }
    725 
    726     {
    727       struct TALER_Amount zero_purse_fee;
    728 
    729       GNUNET_assert (GNUNET_OK ==
    730                      TALER_amount_set_zero (
    731                        pmc->target_amount.currency,
    732                        &zero_purse_fee));
    733       if (GNUNET_OK !=
    734           TALER_wallet_account_merge_verify (
    735             pmc->merge_timestamp,
    736             &pmc->purse_pub,
    737             pmc->purse_expiration,
    738             &pmc->h_contract_terms,
    739             &pmc->target_amount,
    740             &zero_purse_fee,
    741             pmc->min_age,
    742             TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
    743             &pmc->account_pub.reserve_pub,
    744             &pmc->reserve_sig))
    745       {
    746         GNUNET_break_op (0);
    747         return TALER_MHD_reply_with_error (
    748           rc->connection,
    749           MHD_HTTP_FORBIDDEN,
    750           TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
    751           NULL);
    752       }
    753     }
    754     {
    755       struct TALER_FullPayto fake_full_payto;
    756 
    757       GNUNET_asprintf (&fake_full_payto.full_payto,
    758                        "%s?receiver-name=wallet",
    759                        pmc->payto_uri.normalized_payto);
    760       pmc->lch = TEH_legitimization_check (
    761         &rc->async_scope_id,
    762         TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
    763         fake_full_payto,
    764         &pmc->h_payto,
    765         &pmc->account_pub,
    766         &amount_iterator,
    767         pmc,
    768         &legi_result_cb,
    769         pmc);
    770       GNUNET_free (fake_full_payto.full_payto);
    771     }
    772     GNUNET_assert (NULL != pmc->lch);
    773     MHD_suspend_connection (rc->connection);
    774     GNUNET_CONTAINER_DLL_insert (pmc_head,
    775                                  pmc_tail,
    776                                  pmc);
    777     return MHD_YES;
    778   }
    779   if (NULL != pmc->response)
    780   {
    781     return MHD_queue_response (rc->connection,
    782                                pmc->http_status,
    783                                pmc->response);
    784   }
    785   if (! pmc->kyc.ok)
    786     return TEH_RESPONSE_reply_kyc_required (
    787       rc->connection,
    788       &pmc->h_payto,
    789       &pmc->kyc,
    790       false);
    791 
    792   /* execute merge transaction */
    793   {
    794     enum MHD_Result mhd_ret;
    795 
    796     if (GNUNET_OK !=
    797         TEH_DB_run_transaction (rc->connection,
    798                                 "execute purse merge",
    799                                 TEH_MT_REQUEST_PURSE_MERGE,
    800                                 &mhd_ret,
    801                                 &merge_transaction,
    802                                 pmc))
    803     {
    804       return mhd_ret;
    805     }
    806   }
    807 
    808   {
    809     struct TALER_EXCHANGEDB_PurseEventP rep = {
    810       .header.size = htons (sizeof (rep)),
    811       .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
    812       .purse_pub = pmc->purse_pub
    813     };
    814 
    815     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    816                 "Notifying about purse merge\n");
    817     TALER_EXCHANGEDB_event_notify (TEH_pg,
    818                                    &rep.header,
    819                                    NULL,
    820                                    0);
    821   }
    822 
    823   /* generate regular response */
    824   return reply_merge_success (pmc);
    825 }
    826 
    827 
    828 /* end of taler-exchange-httpd_purses_merge.c */