exchange

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

bank_api_credit.c (13412B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2017--2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or
      6   modify it under the terms of the GNU General Public License
      7   as published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file bank-lib/bank_api_credit.c
     21  * @brief Implementation of the /history/incoming
     22  *        requests of the bank's HTTP API.
     23  * @author Christian Grothoff
     24  * @author Marcello Stanisci
     25  */
     26 #include "bank_api_common.h"
     27 #include <microhttpd.h> /* just for HTTP status codes */
     28 #include "taler/taler_signatures.h"
     29 
     30 
     31 /**
     32  * How much longer than the application-specified timeout
     33  * do we wait (giving the server a chance to respond)?
     34  */
     35 #define GRACE_PERIOD_MS 1000
     36 
     37 
     38 /**
     39  * @brief A /history/incoming Handle
     40  */
     41 struct TALER_BANK_CreditHistoryHandle
     42 {
     43 
     44   /**
     45    * The url for this request.
     46    */
     47   char *request_url;
     48 
     49   /**
     50    * Handle for the request.
     51    */
     52   struct GNUNET_CURL_Job *job;
     53 
     54   /**
     55    * Function to call with the result.
     56    */
     57   TALER_BANK_CreditHistoryCallback hcb;
     58 
     59   /**
     60    * Closure for @a cb.
     61    */
     62   void *hcb_cls;
     63 };
     64 
     65 
     66 /**
     67  * Parse history given in JSON format and invoke the callback on each item.
     68  *
     69  * @param hh handle to the account history request
     70  * @param history JSON array with the history
     71  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
     72  *         were set,
     73  *         #GNUNET_SYSERR if there was a protocol violation in @a history
     74  */
     75 static enum GNUNET_GenericReturnValue
     76 parse_account_history (struct TALER_BANK_CreditHistoryHandle *hh,
     77                        const json_t *history)
     78 {
     79   struct TALER_BANK_CreditHistoryResponse chr = {
     80     .http_status = MHD_HTTP_OK,
     81     .ec = TALER_EC_NONE,
     82     .response = history
     83   };
     84   const json_t *history_array;
     85   struct GNUNET_JSON_Specification spec[] = {
     86     GNUNET_JSON_spec_array_const ("incoming_transactions",
     87                                   &history_array),
     88     TALER_JSON_spec_full_payto_uri ("credit_account",
     89                                     &chr.details.ok.credit_account_uri),
     90     GNUNET_JSON_spec_end ()
     91   };
     92 
     93   if (GNUNET_OK !=
     94       GNUNET_JSON_parse (history,
     95                          spec,
     96                          NULL,
     97                          NULL))
     98   {
     99     GNUNET_break_op (0);
    100     return GNUNET_SYSERR;
    101   }
    102   {
    103     size_t len = json_array_size (history_array);
    104     struct TALER_BANK_CreditDetails *cd;
    105 
    106     GNUNET_break_op (0 != len);
    107     cd = GNUNET_new_array (GNUNET_NZL (len),
    108                            struct TALER_BANK_CreditDetails);
    109     for (size_t i = 0; i<len; i++)
    110     {
    111       struct TALER_BANK_CreditDetails *td = &cd[i];
    112       const char *type;
    113       bool no_credit_fee;
    114       struct GNUNET_JSON_Specification hist_spec[] = {
    115         GNUNET_JSON_spec_string ("type",
    116                                  &type),
    117         TALER_JSON_spec_amount_any ("amount",
    118                                     &td->amount),
    119         GNUNET_JSON_spec_mark_optional (
    120           TALER_JSON_spec_amount_any ("credit_fee",
    121                                       &td->credit_fee),
    122           &no_credit_fee),
    123         GNUNET_JSON_spec_timestamp ("date",
    124                                     &td->execution_date),
    125         GNUNET_JSON_spec_uint64 ("row_id",
    126                                  &td->serial_id),
    127         TALER_JSON_spec_full_payto_uri ("debit_account",
    128                                         &td->debit_account_uri),
    129         GNUNET_JSON_spec_end ()
    130       };
    131       json_t *transaction = json_array_get (history_array,
    132                                             i);
    133 
    134       if (GNUNET_OK !=
    135           GNUNET_JSON_parse (transaction,
    136                              hist_spec,
    137                              NULL,
    138                              NULL))
    139       {
    140         GNUNET_break_op (0);
    141         goto fail;
    142       }
    143       if (no_credit_fee)
    144       {
    145         GNUNET_assert (GNUNET_OK ==
    146                        TALER_amount_set_zero (td->amount.currency,
    147                                               &td->credit_fee));
    148       }
    149       else
    150       {
    151         if (GNUNET_YES !=
    152             TALER_amount_cmp_currency (&td->amount,
    153                                        &td->credit_fee))
    154         {
    155           GNUNET_break_op (0);
    156           goto fail;
    157         }
    158       }
    159       if (0 == strcasecmp ("RESERVE",
    160                            type))
    161       {
    162         struct GNUNET_JSON_Specification reserve_spec[] = {
    163           GNUNET_JSON_spec_fixed_auto ("reserve_pub",
    164                                        &td->details.reserve.reserve_pub),
    165           GNUNET_JSON_spec_end ()
    166         };
    167 
    168         if (GNUNET_OK !=
    169             GNUNET_JSON_parse (transaction,
    170                                reserve_spec,
    171                                NULL,
    172                                NULL))
    173         {
    174           GNUNET_break_op (0);
    175           goto fail;
    176         }
    177         td->type = TALER_BANK_CT_RESERVE;
    178       }
    179       else if (0 == strcasecmp ("KYCAUTH",
    180                                 type))
    181       {
    182         struct GNUNET_JSON_Specification kycauth_spec[] = {
    183           GNUNET_JSON_spec_fixed_auto ("account_pub",
    184                                        &td->details.kycauth.account_pub),
    185           GNUNET_JSON_spec_end ()
    186         };
    187 
    188         if (GNUNET_OK !=
    189             GNUNET_JSON_parse (transaction,
    190                                kycauth_spec,
    191                                NULL,
    192                                NULL))
    193         {
    194           GNUNET_break_op (0);
    195           goto fail;
    196         }
    197         td->type = TALER_BANK_CT_KYCAUTH;
    198       }
    199       else if (0 == strcasecmp ("WAD",
    200                                 type))
    201       {
    202         struct GNUNET_JSON_Specification wad_spec[] = {
    203           TALER_JSON_spec_web_url ("origin_exchange_url",
    204                                    &td->details.wad.origin_exchange_url),
    205           GNUNET_JSON_spec_fixed_auto ("wad_id",
    206                                        &td->details.wad.wad_id),
    207           GNUNET_JSON_spec_end ()
    208         };
    209 
    210         if (GNUNET_OK !=
    211             GNUNET_JSON_parse (transaction,
    212                                wad_spec,
    213                                NULL,
    214                                NULL))
    215         {
    216           GNUNET_break_op (0);
    217           goto fail;
    218         }
    219         td->type = TALER_BANK_CT_WAD;
    220       }
    221       else
    222       {
    223         GNUNET_break_op (0);
    224         goto fail;
    225       }
    226     }
    227     chr.details.ok.details_length = len;
    228     chr.details.ok.details = cd;
    229     hh->hcb (hh->hcb_cls,
    230              &chr);
    231     GNUNET_free (cd);
    232     return GNUNET_OK;
    233 fail:
    234     GNUNET_free (cd);
    235     return GNUNET_SYSERR;
    236   }
    237 }
    238 
    239 
    240 /**
    241  * Function called when we're done processing the
    242  * HTTP /history/incoming request.
    243  *
    244  * @param cls the `struct TALER_BANK_CreditHistoryHandle`
    245  * @param response_code HTTP response code, 0 on error
    246  * @param response parsed JSON result, NULL on error
    247  */
    248 static void
    249 handle_credit_history_finished (void *cls,
    250                                 long response_code,
    251                                 const void *response)
    252 {
    253   struct TALER_BANK_CreditHistoryHandle *hh = cls;
    254   struct TALER_BANK_CreditHistoryResponse chr = {
    255     .http_status = response_code,
    256     .response = response
    257   };
    258 
    259   hh->job = NULL;
    260   switch (response_code)
    261   {
    262   case 0:
    263     chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    264     break;
    265   case MHD_HTTP_OK:
    266     if (GNUNET_OK !=
    267         parse_account_history (hh,
    268                                chr.response))
    269     {
    270       GNUNET_break_op (0);
    271       json_dumpf (chr.response,
    272                   stderr,
    273                   JSON_INDENT (2));
    274       chr.http_status = 0;
    275       chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    276       break;
    277     }
    278     TALER_BANK_credit_history_cancel (hh);
    279     return;
    280   case MHD_HTTP_NO_CONTENT:
    281     break;
    282   case MHD_HTTP_BAD_REQUEST:
    283     /* This should never happen, either us or the bank is buggy
    284        (or API version conflict); just pass JSON reply to the application */
    285     GNUNET_break_op (0);
    286     chr.ec = TALER_JSON_get_error_code (chr.response);
    287     break;
    288   case MHD_HTTP_UNAUTHORIZED:
    289     /* Nothing really to verify, bank says the HTTP Authentication
    290        failed. May happen if HTTP authentication is used and the
    291        user supplied a wrong username/password combination. */
    292     chr.ec = TALER_JSON_get_error_code (chr.response);
    293     break;
    294   case MHD_HTTP_NOT_FOUND:
    295     /* Nothing really to verify: the bank is either unaware
    296        of the endpoint (not a bank), or of the account.
    297        We should pass the JSON (?) reply to the application */
    298     chr.ec = TALER_JSON_get_error_code (chr.response);
    299     break;
    300   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    301     /* Server had an internal issue; we should retry, but this API
    302        leaves this to the application */
    303     chr.ec = TALER_JSON_get_error_code (chr.response);
    304     break;
    305   default:
    306     /* unexpected response code */
    307     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    308                 "Unexpected response code %u\n",
    309                 (unsigned int) response_code);
    310     chr.ec = TALER_JSON_get_error_code (chr.response);
    311     break;
    312   }
    313   hh->hcb (hh->hcb_cls,
    314            &chr);
    315   TALER_BANK_credit_history_cancel (hh);
    316 }
    317 
    318 
    319 struct TALER_BANK_CreditHistoryHandle *
    320 TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
    321                            const struct TALER_BANK_AuthenticationData *auth,
    322                            uint64_t start_row,
    323                            int64_t num_results,
    324                            struct GNUNET_TIME_Relative timeout,
    325                            TALER_BANK_CreditHistoryCallback hres_cb,
    326                            void *hres_cb_cls)
    327 {
    328   char url[128];
    329   struct TALER_BANK_CreditHistoryHandle *hh;
    330   CURL *eh;
    331   unsigned long long tms;
    332 
    333   if (0 == num_results)
    334   {
    335     GNUNET_break (0);
    336     return NULL;
    337   }
    338 
    339   tms = (unsigned long long) (timeout.rel_value_us
    340                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    341   if ( ( (UINT64_MAX == start_row) &&
    342          (0 > num_results) ) ||
    343        ( (0 == start_row) &&
    344          (0 < num_results) ) )
    345   {
    346     if ( (0 < num_results) &&
    347          (! GNUNET_TIME_relative_is_zero (timeout)) )
    348       /* 0 == start_row is implied, go with timeout into future */
    349       GNUNET_snprintf (url,
    350                        sizeof (url),
    351                        "history/incoming?limit=%lld&long_poll_ms=%llu",
    352                        (long long) num_results,
    353                        tms);
    354     else
    355       /* Going back from current transaction or have no timeout;
    356          hence timeout makes no sense */
    357       GNUNET_snprintf (url,
    358                        sizeof (url),
    359                        "history/incoming?limit=%lld",
    360                        (long long) num_results);
    361   }
    362   else
    363   {
    364     if ( (0 < num_results) &&
    365          (! GNUNET_TIME_relative_is_zero (timeout)) )
    366       /* going forward from num_result */
    367       GNUNET_snprintf (url,
    368                        sizeof (url),
    369                        "history/incoming?limit=%lld&offset=%llu&long_poll_ms=%llu",
    370                        (long long) num_results,
    371                        (unsigned long long) start_row,
    372                        tms);
    373     else
    374       /* going backwards or have no timeout;
    375          hence timeout makes no sense */
    376       GNUNET_snprintf (url,
    377                        sizeof (url),
    378                        "history/incoming?limit=%lld&offset=%llu",
    379                        (long long) num_results,
    380                        (unsigned long long) start_row);
    381   }
    382   hh = GNUNET_new (struct TALER_BANK_CreditHistoryHandle);
    383   hh->hcb = hres_cb;
    384   hh->hcb_cls = hres_cb_cls;
    385   hh->request_url = TALER_url_join (auth->wire_gateway_url,
    386                                     url,
    387                                     NULL);
    388   if (NULL == hh->request_url)
    389   {
    390     GNUNET_free (hh);
    391     GNUNET_break (0);
    392     return NULL;
    393   }
    394   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    395               "Requesting credit history at `%s'\n",
    396               hh->request_url);
    397   eh = curl_easy_init ();
    398   if ( (NULL == eh) ||
    399        (GNUNET_OK !=
    400         TALER_BANK_setup_auth_ (eh,
    401                                 auth)) ||
    402        (CURLE_OK !=
    403         curl_easy_setopt (eh,
    404                           CURLOPT_URL,
    405                           hh->request_url)) )
    406   {
    407     GNUNET_break (0);
    408     TALER_BANK_credit_history_cancel (hh);
    409     if (NULL != eh)
    410       curl_easy_cleanup (eh);
    411     return NULL;
    412   }
    413   if (0 != tms)
    414   {
    415     GNUNET_break (CURLE_OK ==
    416                   curl_easy_setopt (eh,
    417                                     CURLOPT_TIMEOUT_MS,
    418                                     (long) tms + GRACE_PERIOD_MS));
    419   }
    420   hh->job = GNUNET_CURL_job_add2 (ctx,
    421                                   eh,
    422                                   NULL,
    423                                   &handle_credit_history_finished,
    424                                   hh);
    425   return hh;
    426 }
    427 
    428 
    429 void
    430 TALER_BANK_credit_history_cancel (struct TALER_BANK_CreditHistoryHandle *hh)
    431 {
    432   if (NULL != hh->job)
    433   {
    434     GNUNET_CURL_job_cancel (hh->job);
    435     hh->job = NULL;
    436   }
    437   GNUNET_free (hh->request_url);
    438   GNUNET_free (hh);
    439 }
    440 
    441 
    442 /* end of bank_api_credit.c */