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


      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[GNUNET_NZL (len)];
    105 
    106     GNUNET_break_op (0 != len);
    107     for (size_t i = 0; i<len; i++)
    108     {
    109       struct TALER_BANK_CreditDetails *td = &cd[i];
    110       const char *type;
    111       bool no_credit_fee;
    112       struct GNUNET_JSON_Specification hist_spec[] = {
    113         GNUNET_JSON_spec_string ("type",
    114                                  &type),
    115         TALER_JSON_spec_amount_any ("amount",
    116                                     &td->amount),
    117         GNUNET_JSON_spec_mark_optional (
    118           TALER_JSON_spec_amount_any ("credit_fee",
    119                                       &td->credit_fee),
    120           &no_credit_fee),
    121         GNUNET_JSON_spec_timestamp ("date",
    122                                     &td->execution_date),
    123         GNUNET_JSON_spec_uint64 ("row_id",
    124                                  &td->serial_id),
    125         TALER_JSON_spec_full_payto_uri ("debit_account",
    126                                         &td->debit_account_uri),
    127         GNUNET_JSON_spec_end ()
    128       };
    129       json_t *transaction = json_array_get (history_array,
    130                                             i);
    131 
    132       if (GNUNET_OK !=
    133           GNUNET_JSON_parse (transaction,
    134                              hist_spec,
    135                              NULL,
    136                              NULL))
    137       {
    138         GNUNET_break_op (0);
    139         return GNUNET_SYSERR;
    140       }
    141       if (no_credit_fee)
    142       {
    143         GNUNET_assert (GNUNET_OK ==
    144                        TALER_amount_set_zero (td->amount.currency,
    145                                               &td->credit_fee));
    146       }
    147       else
    148       {
    149         if (GNUNET_YES !=
    150             TALER_amount_cmp_currency (&td->amount,
    151                                        &td->credit_fee))
    152         {
    153           GNUNET_break_op (0);
    154           return GNUNET_SYSERR;
    155         }
    156       }
    157       if (0 == strcasecmp ("RESERVE",
    158                            type))
    159       {
    160         struct GNUNET_JSON_Specification reserve_spec[] = {
    161           GNUNET_JSON_spec_fixed_auto ("reserve_pub",
    162                                        &td->details.reserve.reserve_pub),
    163           GNUNET_JSON_spec_end ()
    164         };
    165 
    166         if (GNUNET_OK !=
    167             GNUNET_JSON_parse (transaction,
    168                                reserve_spec,
    169                                NULL,
    170                                NULL))
    171         {
    172           GNUNET_break_op (0);
    173           return GNUNET_SYSERR;
    174         }
    175         td->type = TALER_BANK_CT_RESERVE;
    176       }
    177       else if (0 == strcasecmp ("KYCAUTH",
    178                                 type))
    179       {
    180         struct GNUNET_JSON_Specification kycauth_spec[] = {
    181           GNUNET_JSON_spec_fixed_auto ("account_pub",
    182                                        &td->details.kycauth.account_pub),
    183           GNUNET_JSON_spec_end ()
    184         };
    185 
    186         if (GNUNET_OK !=
    187             GNUNET_JSON_parse (transaction,
    188                                kycauth_spec,
    189                                NULL,
    190                                NULL))
    191         {
    192           GNUNET_break_op (0);
    193           return GNUNET_SYSERR;
    194         }
    195         td->type = TALER_BANK_CT_KYCAUTH;
    196       }
    197       else if (0 == strcasecmp ("WAD",
    198                                 type))
    199       {
    200         struct GNUNET_JSON_Specification wad_spec[] = {
    201           TALER_JSON_spec_web_url ("origin_exchange_url",
    202                                    &td->details.wad.origin_exchange_url),
    203           GNUNET_JSON_spec_fixed_auto ("wad_id",
    204                                        &td->details.wad.wad_id),
    205           GNUNET_JSON_spec_end ()
    206         };
    207 
    208         if (GNUNET_OK !=
    209             GNUNET_JSON_parse (transaction,
    210                                wad_spec,
    211                                NULL,
    212                                NULL))
    213         {
    214           GNUNET_break_op (0);
    215           return GNUNET_SYSERR;
    216         }
    217         td->type = TALER_BANK_CT_WAD;
    218       }
    219       else
    220       {
    221         GNUNET_break_op (0);
    222         return GNUNET_SYSERR;
    223       }
    224     }
    225     chr.details.ok.details_length = len;
    226     chr.details.ok.details = cd;
    227     hh->hcb (hh->hcb_cls,
    228              &chr);
    229   }
    230   return GNUNET_OK;
    231 }
    232 
    233 
    234 /**
    235  * Function called when we're done processing the
    236  * HTTP /history/incoming request.
    237  *
    238  * @param cls the `struct TALER_BANK_CreditHistoryHandle`
    239  * @param response_code HTTP response code, 0 on error
    240  * @param response parsed JSON result, NULL on error
    241  */
    242 static void
    243 handle_credit_history_finished (void *cls,
    244                                 long response_code,
    245                                 const void *response)
    246 {
    247   struct TALER_BANK_CreditHistoryHandle *hh = cls;
    248   struct TALER_BANK_CreditHistoryResponse chr = {
    249     .http_status = response_code,
    250     .response = response
    251   };
    252 
    253   hh->job = NULL;
    254   switch (response_code)
    255   {
    256   case 0:
    257     chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    258     break;
    259   case MHD_HTTP_OK:
    260     if (GNUNET_OK !=
    261         parse_account_history (hh,
    262                                chr.response))
    263     {
    264       GNUNET_break_op (0);
    265       json_dumpf (chr.response,
    266                   stderr,
    267                   JSON_INDENT (2));
    268       chr.http_status = 0;
    269       chr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    270       break;
    271     }
    272     TALER_BANK_credit_history_cancel (hh);
    273     return;
    274   case MHD_HTTP_NO_CONTENT:
    275     break;
    276   case MHD_HTTP_BAD_REQUEST:
    277     /* This should never happen, either us or the bank is buggy
    278        (or API version conflict); just pass JSON reply to the application */
    279     GNUNET_break_op (0);
    280     chr.ec = TALER_JSON_get_error_code (chr.response);
    281     break;
    282   case MHD_HTTP_UNAUTHORIZED:
    283     /* Nothing really to verify, bank says the HTTP Authentication
    284        failed. May happen if HTTP authentication is used and the
    285        user supplied a wrong username/password combination. */
    286     chr.ec = TALER_JSON_get_error_code (chr.response);
    287     break;
    288   case MHD_HTTP_NOT_FOUND:
    289     /* Nothing really to verify: the bank is either unaware
    290        of the endpoint (not a bank), or of the account.
    291        We should pass the JSON (?) reply to the application */
    292     chr.ec = TALER_JSON_get_error_code (chr.response);
    293     break;
    294   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    295     /* Server had an internal issue; we should retry, but this API
    296        leaves this to the application */
    297     chr.ec = TALER_JSON_get_error_code (chr.response);
    298     break;
    299   default:
    300     /* unexpected response code */
    301     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    302                 "Unexpected response code %u\n",
    303                 (unsigned int) response_code);
    304     chr.ec = TALER_JSON_get_error_code (chr.response);
    305     break;
    306   }
    307   hh->hcb (hh->hcb_cls,
    308            &chr);
    309   TALER_BANK_credit_history_cancel (hh);
    310 }
    311 
    312 
    313 struct TALER_BANK_CreditHistoryHandle *
    314 TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
    315                            const struct TALER_BANK_AuthenticationData *auth,
    316                            uint64_t start_row,
    317                            int64_t num_results,
    318                            struct GNUNET_TIME_Relative timeout,
    319                            TALER_BANK_CreditHistoryCallback hres_cb,
    320                            void *hres_cb_cls)
    321 {
    322   char url[128];
    323   struct TALER_BANK_CreditHistoryHandle *hh;
    324   CURL *eh;
    325   unsigned long long tms;
    326 
    327   if (0 == num_results)
    328   {
    329     GNUNET_break (0);
    330     return NULL;
    331   }
    332 
    333   tms = (unsigned long long) (timeout.rel_value_us
    334                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    335   if ( ( (UINT64_MAX == start_row) &&
    336          (0 > num_results) ) ||
    337        ( (0 == start_row) &&
    338          (0 < num_results) ) )
    339   {
    340     if ( (0 < num_results) &&
    341          (! GNUNET_TIME_relative_is_zero (timeout)) )
    342       /* 0 == start_row is implied, go with timeout into future */
    343       GNUNET_snprintf (url,
    344                        sizeof (url),
    345                        "history/incoming?limit=%lld&long_poll_ms=%llu",
    346                        (long long) num_results,
    347                        tms);
    348     else
    349       /* Going back from current transaction or have no timeout;
    350          hence timeout makes no sense */
    351       GNUNET_snprintf (url,
    352                        sizeof (url),
    353                        "history/incoming?limit=%lld",
    354                        (long long) num_results);
    355   }
    356   else
    357   {
    358     if ( (0 < num_results) &&
    359          (! GNUNET_TIME_relative_is_zero (timeout)) )
    360       /* going forward from num_result */
    361       GNUNET_snprintf (url,
    362                        sizeof (url),
    363                        "history/incoming?limit=%lld&offset=%llu&long_poll_ms=%llu",
    364                        (long long) num_results,
    365                        (unsigned long long) start_row,
    366                        tms);
    367     else
    368       /* going backwards or have no timeout;
    369          hence timeout makes no sense */
    370       GNUNET_snprintf (url,
    371                        sizeof (url),
    372                        "history/incoming?limit=%lld&offset=%llu",
    373                        (long long) num_results,
    374                        (unsigned long long) start_row);
    375   }
    376   hh = GNUNET_new (struct TALER_BANK_CreditHistoryHandle);
    377   hh->hcb = hres_cb;
    378   hh->hcb_cls = hres_cb_cls;
    379   hh->request_url = TALER_url_join (auth->wire_gateway_url,
    380                                     url,
    381                                     NULL);
    382   if (NULL == hh->request_url)
    383   {
    384     GNUNET_free (hh);
    385     GNUNET_break (0);
    386     return NULL;
    387   }
    388   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    389               "Requesting credit history at `%s'\n",
    390               hh->request_url);
    391   eh = curl_easy_init ();
    392   if ( (NULL == eh) ||
    393        (GNUNET_OK !=
    394         TALER_BANK_setup_auth_ (eh,
    395                                 auth)) ||
    396        (CURLE_OK !=
    397         curl_easy_setopt (eh,
    398                           CURLOPT_URL,
    399                           hh->request_url)) )
    400   {
    401     GNUNET_break (0);
    402     TALER_BANK_credit_history_cancel (hh);
    403     if (NULL != eh)
    404       curl_easy_cleanup (eh);
    405     return NULL;
    406   }
    407   if (0 != tms)
    408   {
    409     GNUNET_break (CURLE_OK ==
    410                   curl_easy_setopt (eh,
    411                                     CURLOPT_TIMEOUT_MS,
    412                                     (long) tms + GRACE_PERIOD_MS));
    413   }
    414   hh->job = GNUNET_CURL_job_add2 (ctx,
    415                                   eh,
    416                                   NULL,
    417                                   &handle_credit_history_finished,
    418                                   hh);
    419   return hh;
    420 }
    421 
    422 
    423 void
    424 TALER_BANK_credit_history_cancel (struct TALER_BANK_CreditHistoryHandle *hh)
    425 {
    426   if (NULL != hh->job)
    427   {
    428     GNUNET_CURL_job_cancel (hh->job);
    429     hh->job = NULL;
    430   }
    431   GNUNET_free (hh->request_url);
    432   GNUNET_free (hh);
    433 }
    434 
    435 
    436 /* end of bank_api_credit.c */