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


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