exchange

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

bank_api_registration.c (11402B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 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_registration.c
     19  * @brief Implementation of the POST /registration request of the bank's HTTP API
     20  * @author Christian Grothoff
     21  */
     22 #include "bank_api_common.h"
     23 #include <microhttpd.h> /* just for HTTP status codes */
     24 #include "taler/taler_json_lib.h"
     25 #include "taler/taler_signatures.h"
     26 #include "taler/taler_curl_lib.h"
     27 
     28 
     29 /**
     30  * @brief A /registration Handle
     31  */
     32 struct TALER_BANK_RegistrationHandle
     33 {
     34 
     35   /**
     36    * The URL for this request.
     37    */
     38   char *request_url;
     39 
     40   /**
     41    * POST context.
     42    */
     43   struct TALER_CURL_PostContext post_ctx;
     44 
     45   /**
     46    * Handle for the request.
     47    */
     48   struct GNUNET_CURL_Job *job;
     49 
     50   /**
     51    * Function to call with the result.
     52    */
     53   TALER_BANK_RegistrationCallback cb;
     54 
     55   /**
     56    * Closure for @a cb.
     57    */
     58   void *cb_cls;
     59 
     60 };
     61 
     62 
     63 /**
     64  * Parse the "subject" field of a successful /registration response.
     65  * The field is a JSON object discriminated by "type".
     66  *
     67  * @param subject_json the JSON object to parse (the inner "subject" value)
     68  * @param[out] ts set to the parsed transfer subject on success
     69  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
     70  */
     71 static enum GNUNET_GenericReturnValue
     72 parse_transfer_subject (const json_t *subject_json,
     73                         struct TALER_BANK_TransferSubject *ts)
     74 {
     75   const char *type_str;
     76   struct GNUNET_JSON_Specification type_spec[] = {
     77     GNUNET_JSON_spec_string ("type",
     78                              &type_str),
     79     GNUNET_JSON_spec_end ()
     80   };
     81 
     82   if (GNUNET_OK !=
     83       GNUNET_JSON_parse (subject_json,
     84                          type_spec,
     85                          NULL,
     86                          NULL))
     87   {
     88     GNUNET_break_op (0);
     89     return GNUNET_SYSERR;
     90   }
     91 
     92   if (0 == strcasecmp (type_str,
     93                        "SIMPLE"))
     94   {
     95     struct GNUNET_JSON_Specification spec[] = {
     96       TALER_JSON_spec_amount_any (
     97         "credit_amount",
     98         &ts->details.simple.credit_amount),
     99       GNUNET_JSON_spec_string (
    100         "subject",
    101         (const char **) &ts->details.simple.subject),
    102       GNUNET_JSON_spec_end ()
    103     };
    104 
    105     if (GNUNET_OK !=
    106         GNUNET_JSON_parse (subject_json,
    107                            spec,
    108                            NULL, NULL))
    109     {
    110       GNUNET_break_op (0);
    111       return GNUNET_SYSERR;
    112     }
    113     ts->format = TALER_BANK_SUBJECT_FORMAT_SIMPLE;
    114     return GNUNET_OK;
    115   }
    116   if (0 == strcasecmp (type_str,
    117                        "URI"))
    118   {
    119     struct GNUNET_JSON_Specification spec[] = {
    120       GNUNET_JSON_spec_string (
    121         "uri",
    122         (const char **) &ts->details.uri.uri),
    123       GNUNET_JSON_spec_end ()
    124     };
    125 
    126     if (GNUNET_OK !=
    127         GNUNET_JSON_parse (subject_json,
    128                            spec,
    129                            NULL, NULL))
    130     {
    131       GNUNET_break_op (0);
    132       return GNUNET_SYSERR;
    133     }
    134     ts->format = TALER_BANK_SUBJECT_FORMAT_URI;
    135     return GNUNET_OK;
    136   }
    137   if (0 == strcasecmp (type_str,
    138                        "CH_QR_BILL"))
    139   {
    140     struct GNUNET_JSON_Specification spec[] = {
    141       TALER_JSON_spec_amount_any (
    142         "credit_amount",
    143         &ts->details.ch_qr_bill.credit_amount),
    144       GNUNET_JSON_spec_string (
    145         "qr_reference_number",
    146         (const char **) &ts->details.ch_qr_bill.qr_reference_number),
    147       GNUNET_JSON_spec_end ()
    148     };
    149 
    150     if (GNUNET_OK !=
    151         GNUNET_JSON_parse (subject_json,
    152                            spec,
    153                            NULL, NULL))
    154     {
    155       GNUNET_break_op (0);
    156       return GNUNET_SYSERR;
    157     }
    158     ts->format = TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL;
    159     return GNUNET_OK;
    160   }
    161   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    162               "Unknown transfer subject type `%s'\n",
    163               type_str);
    164   GNUNET_break_op (0);
    165   return GNUNET_SYSERR;
    166 }
    167 
    168 
    169 /**
    170  * Function called when we're done processing the HTTP POST /registration
    171  * request.
    172  *
    173  * @param cls the `struct TALER_BANK_RegistrationHandle`
    174  * @param response_code HTTP response code, 0 on error
    175  * @param response parsed JSON result, NULL on error
    176  */
    177 static void
    178 handle_registration_finished (void *cls,
    179                               long response_code,
    180                               const void *response)
    181 {
    182   struct TALER_BANK_RegistrationHandle *rh = cls;
    183   const json_t *j = response;
    184   struct TALER_BANK_RegistrationResponse rr = {
    185     .http_status = response_code,
    186     .response = response
    187   };
    188 
    189   rh->job = NULL;
    190   switch (response_code)
    191   {
    192   case 0:
    193     rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    194     break;
    195   case MHD_HTTP_OK:
    196     {
    197       const json_t *subjects;
    198       struct GNUNET_JSON_Specification spec[] = {
    199         GNUNET_JSON_spec_array_const ("subjects",
    200                                       &subjects),
    201         GNUNET_JSON_spec_timestamp ("expiration",
    202                                     &rr.details.ok.expiration),
    203         GNUNET_JSON_spec_end ()
    204       };
    205 
    206       if (GNUNET_OK !=
    207           GNUNET_JSON_parse (j,
    208                              spec,
    209                              NULL, NULL))
    210       {
    211         GNUNET_break_op (0);
    212         rr.http_status = 0;
    213         rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    214         break;
    215       }
    216 
    217       {
    218         size_t n = json_array_size (subjects);
    219         struct TALER_BANK_TransferSubject *ts;
    220         size_t i;
    221         const json_t *subject;
    222 
    223         ts = GNUNET_new_array (GNUNET_NZL (n),
    224                                struct TALER_BANK_TransferSubject);
    225         json_array_foreach ((json_t *) subjects, i, subject)
    226         {
    227           if (GNUNET_OK !=
    228               parse_transfer_subject (subject,
    229                                       &ts[i]))
    230           {
    231             GNUNET_break_op (0);
    232             rr.http_status = 0;
    233             rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    234             break;
    235           }
    236         }
    237         if (MHD_HTTP_OK == rr.http_status)
    238         {
    239           rr.details.ok.num_subjects = n;
    240           rr.details.ok.subjects = ts;
    241           rh->cb (rh->cb_cls,
    242                   &rr);
    243           GNUNET_free (ts);
    244           TALER_BANK_registration_cancel (rh);
    245           return;
    246         }
    247         GNUNET_free (ts);
    248       }
    249     }
    250     break;
    251   case MHD_HTTP_BAD_REQUEST:
    252     /* Either we or the service is buggy, or there is an API version conflict. */
    253     GNUNET_break_op (0);
    254     rr.ec = TALER_JSON_get_error_code (j);
    255     break;
    256   case MHD_HTTP_CONFLICT:
    257     /* Covers TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
    258        TALER_EC_BANK_UNSUPPORTED_SUBJECT_FORMAT,
    259        TALER_EC_BANK_DERIVATION_REUSE, and
    260        TALER_EC_BANK_BAD_SIGNATURE. */
    261     rr.ec = TALER_JSON_get_error_code (j);
    262     break;
    263   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    264     /* Server had an internal issue; we should retry, but this API
    265        leaves that to the application. */
    266     rr.ec = TALER_JSON_get_error_code (j);
    267     break;
    268   default:
    269     /* unexpected response code */
    270     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    271                 "Unexpected response code %u\n",
    272                 (unsigned int) response_code);
    273     GNUNET_break (0);
    274     rr.ec = TALER_JSON_get_error_code (j);
    275     break;
    276   }
    277   rh->cb (rh->cb_cls,
    278           &rr);
    279   TALER_BANK_registration_cancel (rh);
    280 }
    281 
    282 
    283 struct TALER_BANK_RegistrationHandle *
    284 TALER_BANK_registration (
    285   struct GNUNET_CURL_Context *ctx,
    286   const char *base_url,
    287   const struct TALER_FullPayto *credit_account,
    288   const struct TALER_Amount *credit_amount,
    289   enum TALER_BankRegistrationType type,
    290   const union TALER_AccountPublicKeyP *account_pub,
    291   const struct TALER_PreparedTransferAuthorizationPrivateKeyP *
    292   authorization_priv,
    293   bool recurrent,
    294   TALER_BANK_RegistrationCallback res_cb,
    295   void *res_cb_cls)
    296 {
    297   struct TALER_PreparedTransferAuthorizationPublicKeyP authorization_pub;
    298   struct TALER_PreparedTransferAuthorizationSignatureP authorization_sig;
    299   struct TALER_BANK_RegistrationHandle *rh;
    300   const char *type_str;
    301   json_t *reg_obj;
    302   CURL *eh;
    303 
    304   TALER_wallet_prepared_transfer_registration_sign (
    305     *credit_account,
    306     credit_amount,
    307     type ==
    308     TALER_BANK_REGISTRATION_TYPE_RESERVE,
    309     recurrent,
    310     account_pub,
    311     authorization_priv,
    312     &authorization_sig);
    313   GNUNET_CRYPTO_eddsa_key_get_public (&authorization_priv->eddsa_priv,
    314                                       &authorization_pub.eddsa_pub);
    315 
    316   switch (type)
    317   {
    318   case TALER_BANK_REGISTRATION_TYPE_RESERVE:
    319     type_str = "reserve";
    320     break;
    321   case TALER_BANK_REGISTRATION_TYPE_KYC:
    322     type_str = "kyc";
    323     break;
    324   default:
    325     GNUNET_break (0);
    326     return NULL;
    327   }
    328 
    329   reg_obj = GNUNET_JSON_PACK (
    330     TALER_JSON_pack_full_payto ("credit_account",
    331                                 *credit_account),
    332     TALER_JSON_pack_amount ("credit_amount",
    333                             credit_amount),
    334     GNUNET_JSON_pack_string ("type",
    335                              type_str),
    336     GNUNET_JSON_pack_string ("alg",
    337                              "EdDSA"),
    338     GNUNET_JSON_pack_data_auto ("account_pub",
    339                                 account_pub),
    340     GNUNET_JSON_pack_data_auto ("authorization_pub",
    341                                 &authorization_pub),
    342     GNUNET_JSON_pack_data_auto ("authorization_sig",
    343                                 &authorization_sig),
    344     GNUNET_JSON_pack_bool ("recurrent",
    345                            recurrent));
    346   rh = GNUNET_new (struct TALER_BANK_RegistrationHandle);
    347   rh->cb = res_cb;
    348   rh->cb_cls = res_cb_cls;
    349   rh->request_url = TALER_url_join (base_url,
    350                                     "registration",
    351                                     NULL);
    352   if (NULL == rh->request_url)
    353   {
    354     GNUNET_free (rh);
    355     json_decref (reg_obj);
    356     return NULL;
    357   }
    358   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    359               "Requesting wire transfer subject registration at `%s'\n",
    360               rh->request_url);
    361   eh = curl_easy_init ();
    362   if ( (NULL == eh) ||
    363        (CURLE_OK !=
    364         curl_easy_setopt (eh,
    365                           CURLOPT_URL,
    366                           rh->request_url)) ||
    367        (GNUNET_OK !=
    368         TALER_curl_easy_post (&rh->post_ctx,
    369                               eh,
    370                               reg_obj)) )
    371   {
    372     GNUNET_break (0);
    373     TALER_BANK_registration_cancel (rh);
    374     if (NULL != eh)
    375       curl_easy_cleanup (eh);
    376     json_decref (reg_obj);
    377     return NULL;
    378   }
    379   json_decref (reg_obj);
    380   rh->job = GNUNET_CURL_job_add2 (ctx,
    381                                   eh,
    382                                   rh->post_ctx.headers,
    383                                   &handle_registration_finished,
    384                                   rh);
    385   return rh;
    386 }
    387 
    388 
    389 void
    390 TALER_BANK_registration_cancel (
    391   struct TALER_BANK_RegistrationHandle *rh)
    392 {
    393   if (NULL != rh->job)
    394   {
    395     GNUNET_CURL_job_cancel (rh->job);
    396     rh->job = NULL;
    397   }
    398   TALER_curl_easy_post_finished (&rh->post_ctx);
    399   GNUNET_free (rh->request_url);
    400   GNUNET_free (rh);
    401 }
    402 
    403 
    404 /* end of bank_api_registration.c */