exchange

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

bank_api_transfer.c (10485B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2015--2023, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file bank-lib/bank_api_transfer.c
     19  * @brief Implementation of the /transfer/ requests of the bank's HTTP API
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include "bank_api_common.h"
     24 #include <microhttpd.h> /* just for HTTP status codes */
     25 #include "taler/taler_signatures.h"
     26 #include "taler/taler_curl_lib.h"
     27 #include "taler/taler_bank_service.h"
     28 
     29 
     30 GNUNET_NETWORK_STRUCT_BEGIN
     31 
     32 /**
     33  * Data structure serialized in the prepare stage.
     34  */
     35 struct WirePackP
     36 {
     37   /**
     38    * Random unique identifier for the request.
     39    */
     40   struct GNUNET_HashCode request_uid;
     41 
     42   /**
     43    * Amount to be transferred.
     44    */
     45   struct TALER_AmountNBO amount;
     46 
     47   /**
     48    * Wire transfer identifier to use.
     49    */
     50   struct TALER_WireTransferIdentifierRawP wtid;
     51 
     52   /**
     53    * Length of the payto:// URL of the target account,
     54    * including 0-terminator, in network byte order.
     55    */
     56   uint32_t account_len GNUNET_PACKED;
     57 
     58   /**
     59    * Length of the exchange's base URL,
     60    * including 0-terminator, in network byte order.
     61    */
     62   uint32_t exchange_url_len GNUNET_PACKED;
     63 
     64 };
     65 
     66 GNUNET_NETWORK_STRUCT_END
     67 
     68 
     69 void
     70 TALER_BANK_prepare_transfer (
     71   const struct TALER_FullPayto destination_account_payto_uri,
     72   const struct TALER_Amount *amount,
     73   const char *exchange_base_url,
     74   const struct TALER_WireTransferIdentifierRawP *wtid,
     75   const char *extra_wire_transfer_subject,
     76   void **buf,
     77   size_t *buf_size)
     78 {
     79   const char *payto = destination_account_payto_uri.full_payto;
     80   struct WirePackP *wp;
     81   size_t d_len = strlen (payto) + 1;
     82   size_t u_len = strlen (exchange_base_url) + 1;
     83   size_t x_len = (NULL == extra_wire_transfer_subject)
     84     ? 0
     85     : strlen (extra_wire_transfer_subject) + 1;
     86   char *end;
     87 
     88   if ( (d_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
     89        (u_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
     90        (x_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
     91        (d_len + u_len + x_len + sizeof (*wp) >= GNUNET_MAX_MALLOC_CHECKED) )
     92   {
     93     GNUNET_break (0); /* that's some long URL... */
     94     *buf = NULL;
     95     *buf_size = 0;
     96     return;
     97   }
     98   *buf_size = sizeof (*wp) + d_len + u_len + x_len;
     99   wp = GNUNET_malloc (*buf_size);
    100   GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,
    101                                     &wp->request_uid);
    102   TALER_amount_hton (&wp->amount,
    103                      amount);
    104   wp->wtid = *wtid;
    105   wp->account_len = htonl ((uint32_t) d_len);
    106   wp->exchange_url_len = htonl ((uint32_t) u_len);
    107   end = (char *) &wp[1];
    108   GNUNET_memcpy (end,
    109                  payto,
    110                  d_len);
    111   GNUNET_memcpy (end + d_len,
    112                  exchange_base_url,
    113                  u_len);
    114   GNUNET_memcpy (end + d_len + u_len,
    115                  extra_wire_transfer_subject,
    116                  x_len);
    117   *buf = (char *) wp;
    118 }
    119 
    120 
    121 /**
    122  * @brief Handle for an active wire transfer.
    123  */
    124 struct TALER_BANK_TransferHandle
    125 {
    126 
    127   /**
    128    * The url for this request.
    129    */
    130   char *request_url;
    131 
    132   /**
    133    * POST context.
    134    */
    135   struct TALER_CURL_PostContext post_ctx;
    136 
    137   /**
    138    * Handle for the request.
    139    */
    140   struct GNUNET_CURL_Job *job;
    141 
    142   /**
    143    * Function to call with the result.
    144    */
    145   TALER_BANK_TransferCallback cb;
    146 
    147   /**
    148    * Closure for @a cb.
    149    */
    150   void *cb_cls;
    151 
    152 };
    153 
    154 
    155 /**
    156  * Function called when we're done processing the
    157  * HTTP /transfer request.
    158  *
    159  * @param cls the `struct TALER_BANK_TransferHandle`
    160  * @param response_code HTTP response code, 0 on error
    161  * @param response parsed JSON result, NULL on error
    162  */
    163 static void
    164 handle_transfer_finished (void *cls,
    165                           long response_code,
    166                           const void *response)
    167 {
    168   struct TALER_BANK_TransferHandle *th = cls;
    169   const json_t *j = response;
    170   struct TALER_BANK_TransferResponse tr = {
    171     .http_status = response_code,
    172     .response = j
    173   };
    174 
    175   th->job = NULL;
    176   switch (response_code)
    177   {
    178   case 0:
    179     tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    180     break;
    181   case MHD_HTTP_OK:
    182     {
    183       struct GNUNET_JSON_Specification spec[] = {
    184         GNUNET_JSON_spec_uint64 ("row_id",
    185                                  &tr.details.ok.row_id),
    186         GNUNET_JSON_spec_timestamp ("timestamp",
    187                                     &tr.details.ok.timestamp),
    188         GNUNET_JSON_spec_end ()
    189       };
    190 
    191       if (GNUNET_OK !=
    192           GNUNET_JSON_parse (j,
    193                              spec,
    194                              NULL, NULL))
    195       {
    196         GNUNET_break_op (0);
    197         tr.http_status = 0;
    198         tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    199         break;
    200       }
    201     }
    202     break;
    203   case MHD_HTTP_BAD_REQUEST:
    204     /* This should never happen, either us or the bank is buggy
    205        (or API version conflict); just pass JSON reply to the application */
    206     GNUNET_break_op (0);
    207     tr.ec = TALER_JSON_get_error_code (j);
    208     break;
    209   case MHD_HTTP_UNAUTHORIZED:
    210     /* Nothing really to verify, bank says our credentials are
    211        invalid. We should pass the JSON reply to the application. */
    212     tr.ec = TALER_JSON_get_error_code (j);
    213     break;
    214   case MHD_HTTP_NOT_FOUND:
    215     /* Nothing really to verify, endpoint wrong -- could be user unknown */
    216     tr.ec = TALER_JSON_get_error_code (j);
    217     break;
    218   case MHD_HTTP_CONFLICT:
    219     /* Nothing really to verify. Server says we used the same transfer request
    220        UID before, but with different details.  Should not happen if the user
    221        properly used #TALER_BANK_prepare_transfer() and our PRNG is not
    222        broken... */
    223     tr.ec = TALER_JSON_get_error_code (j);
    224     break;
    225   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    226     /* Server had an internal issue; we should retry, but this API
    227        leaves this to the application */
    228     tr.ec = TALER_JSON_get_error_code (j);
    229     break;
    230   default:
    231     /* unexpected response code */
    232     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    233                 "Unexpected response code %u\n",
    234                 (unsigned int) response_code);
    235     GNUNET_break (0);
    236     tr.ec = TALER_JSON_get_error_code (j);
    237     break;
    238   }
    239   th->cb (th->cb_cls,
    240           &tr);
    241   TALER_BANK_transfer_cancel (th);
    242 }
    243 
    244 
    245 struct TALER_BANK_TransferHandle *
    246 TALER_BANK_transfer (
    247   struct GNUNET_CURL_Context *ctx,
    248   const struct TALER_BANK_AuthenticationData *auth,
    249   const void *buf,
    250   size_t buf_size,
    251   TALER_BANK_TransferCallback cc,
    252   void *cc_cls)
    253 {
    254   struct TALER_BANK_TransferHandle *th;
    255   json_t *transfer_obj;
    256   CURL *eh;
    257   const struct WirePackP *wp = buf;
    258   uint32_t d_len;
    259   uint32_t u_len;
    260   uint32_t x_len;
    261   const char *destination_account_uri;
    262   const char *exchange_base_url;
    263   const char *extra_metadata;
    264   struct TALER_Amount amount;
    265 
    266   if (sizeof (*wp) > buf_size)
    267   {
    268     GNUNET_break (0);
    269     return NULL;
    270   }
    271   d_len = ntohl (wp->account_len);
    272   u_len = ntohl (wp->exchange_url_len);
    273   if ( (sizeof (*wp) + d_len + u_len > buf_size) ||
    274        (d_len > buf_size) ||
    275        (u_len > buf_size) ||
    276        (d_len + u_len > buf_size) )
    277   {
    278     GNUNET_break (0);
    279     return NULL;
    280   }
    281   x_len = buf_size - (sizeof (*wp) + d_len + u_len);
    282   destination_account_uri = (const char *) &wp[1];
    283   exchange_base_url = destination_account_uri + d_len;
    284   if ( ('\0' != destination_account_uri[d_len - 1]) ||
    285        ('\0' != exchange_base_url[u_len - 1]) )
    286   {
    287     GNUNET_break (0);
    288     return NULL;
    289   }
    290   if (0 != x_len)
    291   {
    292     extra_metadata = destination_account_uri + d_len + u_len;
    293     if ('\0' != extra_metadata[x_len - 1])
    294     {
    295       GNUNET_break (0);
    296       return NULL;
    297     }
    298   }
    299   else
    300   {
    301     extra_metadata = NULL;
    302   }
    303 
    304   if (NULL == auth->wire_gateway_url)
    305   {
    306     GNUNET_break (0);
    307     return NULL;
    308   }
    309   TALER_amount_ntoh (&amount,
    310                      &wp->amount);
    311   th = GNUNET_new (struct TALER_BANK_TransferHandle);
    312   th->cb = cc;
    313   th->cb_cls = cc_cls;
    314   th->request_url = TALER_url_join (auth->wire_gateway_url,
    315                                     "transfer",
    316                                     NULL);
    317   if (NULL == th->request_url)
    318   {
    319     GNUNET_free (th);
    320     GNUNET_break (0);
    321     return NULL;
    322   }
    323   transfer_obj = GNUNET_JSON_PACK (
    324     GNUNET_JSON_pack_data_auto ("request_uid",
    325                                 &wp->request_uid),
    326     TALER_JSON_pack_amount ("amount",
    327                             &amount),
    328     GNUNET_JSON_pack_string ("exchange_base_url",
    329                              exchange_base_url),
    330     GNUNET_JSON_pack_allow_null (
    331       GNUNET_JSON_pack_string ("metadata",
    332                                extra_metadata)),
    333     GNUNET_JSON_pack_data_auto ("wtid",
    334                                 &wp->wtid),
    335     GNUNET_JSON_pack_string ("credit_account",
    336                              destination_account_uri));
    337   if (NULL == transfer_obj)
    338   {
    339     GNUNET_break (0);
    340     return NULL;
    341   }
    342   eh = curl_easy_init ();
    343   if ( (NULL == eh) ||
    344        (GNUNET_OK !=
    345         TALER_BANK_setup_auth_ (eh,
    346                                 auth)) ||
    347        (CURLE_OK !=
    348         curl_easy_setopt (eh,
    349                           CURLOPT_URL,
    350                           th->request_url)) ||
    351        (GNUNET_OK !=
    352         TALER_curl_easy_post (&th->post_ctx,
    353                               eh,
    354                               transfer_obj)) )
    355   {
    356     GNUNET_break (0);
    357     TALER_BANK_transfer_cancel (th);
    358     if (NULL != eh)
    359       curl_easy_cleanup (eh);
    360     json_decref (transfer_obj);
    361     return NULL;
    362   }
    363   json_decref (transfer_obj);
    364   th->job = GNUNET_CURL_job_add2 (ctx,
    365                                   eh,
    366                                   th->post_ctx.headers,
    367                                   &handle_transfer_finished,
    368                                   th);
    369   return th;
    370 }
    371 
    372 
    373 void
    374 TALER_BANK_transfer_cancel (struct TALER_BANK_TransferHandle *th)
    375 {
    376   if (NULL != th->job)
    377   {
    378     GNUNET_CURL_job_cancel (th->job);
    379     th->job = NULL;
    380   }
    381   TALER_curl_easy_post_finished (&th->post_ctx);
    382   GNUNET_free (th->request_url);
    383   GNUNET_free (th);
    384 }
    385 
    386 
    387 /* end of bank_api_transfer.c */