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


      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       TALER_JSON_spec_amount_any (
    121         "credit_amount",
    122         &ts->details.uri.credit_amount),
    123       GNUNET_JSON_spec_string (
    124         "uri",
    125         (const char **) &ts->details.uri.uri),
    126       GNUNET_JSON_spec_end ()
    127     };
    128 
    129     if (GNUNET_OK !=
    130         GNUNET_JSON_parse (subject_json,
    131                            spec,
    132                            NULL, NULL))
    133     {
    134       GNUNET_break_op (0);
    135       return GNUNET_SYSERR;
    136     }
    137     ts->format = TALER_BANK_SUBJECT_FORMAT_URI;
    138     return GNUNET_OK;
    139   }
    140   if (0 == strcasecmp (type_str,
    141                        "CH_QR_BILL"))
    142   {
    143     struct GNUNET_JSON_Specification spec[] = {
    144       TALER_JSON_spec_amount_any (
    145         "credit_amount",
    146         &ts->details.ch_qr_bill.credit_amount),
    147       GNUNET_JSON_spec_string (
    148         "qr_reference_number",
    149         (const char **) &ts->details.ch_qr_bill.qr_reference_number),
    150       GNUNET_JSON_spec_end ()
    151     };
    152 
    153     if (GNUNET_OK !=
    154         GNUNET_JSON_parse (subject_json,
    155                            spec,
    156                            NULL, NULL))
    157     {
    158       GNUNET_break_op (0);
    159       return GNUNET_SYSERR;
    160     }
    161     ts->format = TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL;
    162     return GNUNET_OK;
    163   }
    164   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    165               "Unknown transfer subject type `%s'\n",
    166               type_str);
    167   GNUNET_break_op (0);
    168   return GNUNET_SYSERR;
    169 }
    170 
    171 
    172 /**
    173  * Function called when we're done processing the HTTP POST /registration
    174  * request.
    175  *
    176  * @param cls the `struct TALER_BANK_RegistrationHandle`
    177  * @param response_code HTTP response code, 0 on error
    178  * @param response parsed JSON result, NULL on error
    179  */
    180 static void
    181 handle_registration_finished (void *cls,
    182                               long response_code,
    183                               const void *response)
    184 {
    185   struct TALER_BANK_RegistrationHandle *rh = cls;
    186   const json_t *j = response;
    187   struct TALER_BANK_RegistrationResponse rr = {
    188     .http_status = response_code,
    189     .response = response
    190   };
    191 
    192   rh->job = NULL;
    193   switch (response_code)
    194   {
    195   case 0:
    196     rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    197     break;
    198   case MHD_HTTP_OK:
    199     {
    200       const json_t *subjects;
    201       struct GNUNET_JSON_Specification spec[] = {
    202         GNUNET_JSON_spec_array_const ("subjects",
    203                                       &subjects),
    204         GNUNET_JSON_spec_timestamp ("expiration",
    205                                     &rr.details.ok.expiration),
    206         GNUNET_JSON_spec_end ()
    207       };
    208 
    209       if (GNUNET_OK !=
    210           GNUNET_JSON_parse (j,
    211                              spec,
    212                              NULL, NULL))
    213       {
    214         GNUNET_break_op (0);
    215         rr.http_status = 0;
    216         rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    217         break;
    218       }
    219 
    220       {
    221         size_t n = json_array_size (subjects);
    222         struct TALER_BANK_TransferSubject *ts;
    223         size_t i;
    224         const json_t *subject;
    225 
    226         ts = GNUNET_new_array (GNUNET_NZL (n),
    227                                struct TALER_BANK_TransferSubject);
    228         json_array_foreach ((json_t *) subjects, i, subject)
    229         {
    230           if (GNUNET_OK !=
    231               parse_transfer_subject (subject,
    232                                       &ts[i]))
    233           {
    234             GNUNET_break_op (0);
    235             rr.http_status = 0;
    236             rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    237             break;
    238           }
    239         }
    240         if (MHD_HTTP_OK == rr.http_status)
    241         {
    242           rr.details.ok.num_subjects = n;
    243           rr.details.ok.subjects = ts;
    244           rh->cb (rh->cb_cls,
    245                   &rr);
    246           GNUNET_free (ts);
    247           TALER_BANK_registration_cancel (rh);
    248           return;
    249         }
    250         GNUNET_free (ts);
    251       }
    252     }
    253     break;
    254   case MHD_HTTP_BAD_REQUEST:
    255     /* Either we or the service is buggy, or there is an API version conflict. */
    256     GNUNET_break_op (0);
    257     rr.ec = TALER_JSON_get_error_code (j);
    258     break;
    259   case MHD_HTTP_CONFLICT:
    260     /* Covers TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
    261        TALER_EC_BANK_UNSUPPORTED_SUBJECT_FORMAT,
    262        TALER_EC_BANK_DERIVATION_REUSE, and
    263        TALER_EC_BANK_BAD_SIGNATURE. */
    264     rr.ec = TALER_JSON_get_error_code (j);
    265     break;
    266   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    267     /* Server had an internal issue; we should retry, but this API
    268        leaves that to the application. */
    269     rr.ec = TALER_JSON_get_error_code (j);
    270     break;
    271   default:
    272     /* unexpected response code */
    273     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    274                 "Unexpected response code %u\n",
    275                 (unsigned int) response_code);
    276     GNUNET_break (0);
    277     rr.ec = TALER_JSON_get_error_code (j);
    278     break;
    279   }
    280   rh->cb (rh->cb_cls,
    281           &rr);
    282   TALER_BANK_registration_cancel (rh);
    283 }
    284 
    285 
    286 struct TALER_BANK_RegistrationHandle *
    287 TALER_BANK_registration (
    288   struct GNUNET_CURL_Context *ctx,
    289   const char *base_url,
    290   const struct TALER_FullPayto *credit_account,
    291   const struct TALER_Amount *credit_amount,
    292   enum TALER_BankRegistrationType type,
    293   const union TALER_AccountPublicKeyP *account_pub,
    294   const struct TALER_PreparedTransferAuthorizationPrivateKeyP *
    295   authorization_priv,
    296   bool recurrent,
    297   TALER_BANK_RegistrationCallback res_cb,
    298   void *res_cb_cls)
    299 {
    300   struct TALER_PreparedTransferAuthorizationPublicKeyP authorization_pub;
    301   struct TALER_PreparedTransferAuthorizationSignatureP authorization_sig;
    302   struct TALER_BANK_RegistrationHandle *rh;
    303   const char *type_str;
    304   json_t *reg_obj;
    305   CURL *eh;
    306 
    307   TALER_wallet_prepared_transfer_registration_sign (
    308     *credit_account,
    309     credit_amount,
    310     type,
    311     recurrent,
    312     account_pub,
    313     authorization_priv,
    314     &authorization_sig);
    315   GNUNET_CRYPTO_eddsa_key_get_public (&authorization_priv->eddsa_priv,
    316                                       &authorization_pub.eddsa_pub);
    317 
    318   switch (type)
    319   {
    320   case TALER_BANK_REGISTRATION_TYPE_RESERVE:
    321     type_str = "reserve";
    322     break;
    323   case TALER_BANK_REGISTRATION_TYPE_KYC:
    324     type_str = "kyc";
    325     break;
    326   default:
    327     GNUNET_break (0);
    328     return NULL;
    329   }
    330 
    331   reg_obj = GNUNET_JSON_PACK (
    332     TALER_JSON_pack_full_payto ("credit_account",
    333                                 *credit_account),
    334     TALER_JSON_pack_amount ("credit_amount",
    335                             credit_amount),
    336     GNUNET_JSON_pack_string ("type",
    337                              type_str),
    338     GNUNET_JSON_pack_string ("alg",
    339                              "EdDSA"),
    340     GNUNET_JSON_pack_data_auto ("account_pub",
    341                                 account_pub),
    342     GNUNET_JSON_pack_data_auto ("authorization_pub",
    343                                 &authorization_pub),
    344     GNUNET_JSON_pack_data_auto ("authorization_sig",
    345                                 &authorization_sig),
    346     GNUNET_JSON_pack_bool ("recurrent",
    347                            recurrent));
    348   rh = GNUNET_new (struct TALER_BANK_RegistrationHandle);
    349   rh->cb = res_cb;
    350   rh->cb_cls = res_cb_cls;
    351   rh->request_url = TALER_url_join (base_url,
    352                                     "registration",
    353                                     NULL);
    354   if (NULL == rh->request_url)
    355   {
    356     GNUNET_free (rh);
    357     json_decref (reg_obj);
    358     return NULL;
    359   }
    360   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    361               "Requesting wire transfer subject registration at `%s'\n",
    362               rh->request_url);
    363   eh = curl_easy_init ();
    364   if ( (NULL == eh) ||
    365        (CURLE_OK !=
    366         curl_easy_setopt (eh,
    367                           CURLOPT_URL,
    368                           rh->request_url)) ||
    369        (GNUNET_OK !=
    370         TALER_curl_easy_post (&rh->post_ctx,
    371                               eh,
    372                               reg_obj)) )
    373   {
    374     GNUNET_break (0);
    375     TALER_BANK_registration_cancel (rh);
    376     if (NULL != eh)
    377       curl_easy_cleanup (eh);
    378     json_decref (reg_obj);
    379     return NULL;
    380   }
    381   json_decref (reg_obj);
    382   rh->job = GNUNET_CURL_job_add2 (ctx,
    383                                   eh,
    384                                   rh->post_ctx.headers,
    385                                   &handle_registration_finished,
    386                                   rh);
    387   if (NULL == rh->job)
    388   {
    389     GNUNET_break (0);
    390     curl_easy_cleanup (eh);
    391     TALER_BANK_registration_cancel (rh);
    392     return NULL;
    393   }
    394   return rh;
    395 }
    396 
    397 
    398 void
    399 TALER_BANK_registration_cancel (
    400   struct TALER_BANK_RegistrationHandle *rh)
    401 {
    402   if (NULL != rh->job)
    403   {
    404     GNUNET_CURL_job_cancel (rh->job);
    405     rh->job = NULL;
    406   }
    407   TALER_curl_easy_post_finished (&rh->post_ctx);
    408   GNUNET_free (rh->request_url);
    409   GNUNET_free (rh);
    410 }
    411 
    412 
    413 /* end of bank_api_registration.c */