exchange

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

bank_api_debit.c (11323B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2017--2023 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_debit.c
     21  * @brief Implementation of the /history/outgoing
     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/outgoing Handle
     40  */
     41 struct TALER_BANK_DebitHistoryHandle
     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_DebitHistoryCallback 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_DebitHistoryHandle *hh,
     77                        const json_t *history)
     78 {
     79   struct TALER_BANK_DebitHistoryResponse dhr = {
     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 ("outgoing_transactions",
     87                                   &history_array),
     88     TALER_JSON_spec_full_payto_uri ("debit_account",
     89                                     &dhr.details.ok.debit_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_DebitDetails *dd;
    105 
    106     GNUNET_break_op (0 != len);
    107     dd = GNUNET_new_array (GNUNET_NZL (len),
    108                            struct TALER_BANK_DebitDetails);
    109     for (unsigned int i = 0; i<len; i++)
    110     {
    111       struct TALER_BANK_DebitDetails *td = &dd[i];
    112       bool no_debit_fee;
    113       struct GNUNET_JSON_Specification hist_spec[] = {
    114         TALER_JSON_spec_amount_any ("amount",
    115                                     &td->amount),
    116         GNUNET_JSON_spec_mark_optional (
    117           TALER_JSON_spec_amount_any ("debit_fee",
    118                                       &td->debit_fee),
    119           &no_debit_fee),
    120         GNUNET_JSON_spec_timestamp ("date",
    121                                     &td->execution_date),
    122         GNUNET_JSON_spec_uint64 ("row_id",
    123                                  &td->serial_id),
    124         GNUNET_JSON_spec_fixed_auto ("wtid",
    125                                      &td->wtid),
    126         TALER_JSON_spec_full_payto_uri ("credit_account",
    127                                         &td->credit_account_uri),
    128         TALER_JSON_spec_web_url ("exchange_base_url",
    129                                  &td->exchange_base_url),
    130         GNUNET_JSON_spec_mark_optional (
    131           GNUNET_JSON_spec_string ("metadata",
    132                                    &td->metadata),
    133           NULL),
    134         GNUNET_JSON_spec_end ()
    135       };
    136       json_t *transaction = json_array_get (history_array,
    137                                             i);
    138 
    139       if (GNUNET_OK !=
    140           GNUNET_JSON_parse (transaction,
    141                              hist_spec,
    142                              NULL,
    143                              NULL))
    144       {
    145         GNUNET_break_op (0);
    146         GNUNET_free (dd);
    147         return GNUNET_SYSERR;
    148       }
    149       if (no_debit_fee)
    150       {
    151         GNUNET_assert (GNUNET_OK ==
    152                        TALER_amount_set_zero (td->amount.currency,
    153                                               &td->debit_fee));
    154       }
    155       else if (GNUNET_YES !=
    156                TALER_amount_cmp_currency (&td->amount,
    157                                           &td->debit_fee))
    158       {
    159         GNUNET_break_op (0);
    160         GNUNET_free (dd);
    161         return GNUNET_SYSERR;
    162       }
    163     }
    164     dhr.details.ok.details_length = len;
    165     dhr.details.ok.details = dd;
    166     hh->hcb (hh->hcb_cls,
    167              &dhr);
    168     GNUNET_free (dd);
    169   }
    170   return GNUNET_OK;
    171 }
    172 
    173 
    174 /**
    175  * Function called when we're done processing the
    176  * HTTP /history/outgoing request.
    177  *
    178  * @param cls the `struct TALER_BANK_DebitHistoryHandle`
    179  * @param response_code HTTP response code, 0 on error
    180  * @param response parsed JSON result, NULL on error
    181  */
    182 static void
    183 handle_debit_history_finished (void *cls,
    184                                long response_code,
    185                                const void *response)
    186 {
    187   struct TALER_BANK_DebitHistoryHandle *hh = cls;
    188   struct TALER_BANK_DebitHistoryResponse dhr = {
    189     .http_status = response_code,
    190     .response = response
    191   };
    192 
    193   hh->job = NULL;
    194   switch (response_code)
    195   {
    196   case 0:
    197     dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    198     break;
    199   case MHD_HTTP_OK:
    200     if (GNUNET_OK !=
    201         parse_account_history (hh,
    202                                dhr.response))
    203     {
    204       GNUNET_break_op (0);
    205       dhr.http_status = 0;
    206       dhr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    207       break;
    208     }
    209     TALER_BANK_debit_history_cancel (hh);
    210     return;
    211   case MHD_HTTP_NO_CONTENT:
    212     break;
    213   case MHD_HTTP_BAD_REQUEST:
    214     /* This should never happen, either us or the bank is buggy
    215        (or API version conflict); just pass JSON reply to the application */
    216     GNUNET_break_op (0);
    217     dhr.ec = TALER_JSON_get_error_code (dhr.response);
    218     break;
    219   case MHD_HTTP_UNAUTHORIZED:
    220     /* Nothing really to verify, bank says the HTTP Authentication
    221        failed. May happen if HTTP authentication is used and the
    222        user supplied a wrong username/password combination. */
    223     dhr.ec = TALER_JSON_get_error_code (dhr.response);
    224     break;
    225   case MHD_HTTP_NOT_FOUND:
    226     /* Nothing really to verify: the bank is either unaware
    227        of the endpoint (not a bank), or of the account.
    228        We should pass the JSON (?) reply to the application */
    229     dhr.ec = TALER_JSON_get_error_code (dhr.response);
    230     break;
    231   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    232     /* Server had an internal issue; we should retry, but this API
    233        leaves this to the application */
    234     dhr.ec = TALER_JSON_get_error_code (dhr.response);
    235     break;
    236   default:
    237     /* unexpected response code */
    238     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    239                 "Unexpected response code %u\n",
    240                 (unsigned int) response_code);
    241     dhr.ec = TALER_JSON_get_error_code (dhr.response);
    242     break;
    243   }
    244   hh->hcb (hh->hcb_cls,
    245            &dhr);
    246   TALER_BANK_debit_history_cancel (hh);
    247 }
    248 
    249 
    250 struct TALER_BANK_DebitHistoryHandle *
    251 TALER_BANK_debit_history (struct GNUNET_CURL_Context *ctx,
    252                           const struct TALER_BANK_AuthenticationData *auth,
    253                           uint64_t start_row,
    254                           int64_t num_results,
    255                           struct GNUNET_TIME_Relative timeout,
    256                           TALER_BANK_DebitHistoryCallback hres_cb,
    257                           void *hres_cb_cls)
    258 {
    259   char url[128];
    260   struct TALER_BANK_DebitHistoryHandle *hh;
    261   CURL *eh;
    262   unsigned long long tms;
    263 
    264   if (0 == num_results)
    265   {
    266     GNUNET_break (0);
    267     return NULL;
    268   }
    269 
    270   tms = (unsigned long long) (timeout.rel_value_us
    271                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    272   if ( ( (UINT64_MAX == start_row) &&
    273          (0 > num_results) ) ||
    274        ( (0 == start_row) &&
    275          (0 < num_results) ) )
    276   {
    277     if ( (0 < num_results) &&
    278          (! GNUNET_TIME_relative_is_zero (timeout)) )
    279       GNUNET_snprintf (url,
    280                        sizeof (url),
    281                        "history/outgoing?limit=%lld&timeout_ms=%llu",
    282                        (long long) num_results,
    283                        tms);
    284     else
    285       GNUNET_snprintf (url,
    286                        sizeof (url),
    287                        "history/outgoing?limit=%lld",
    288                        (long long) num_results);
    289   }
    290   else
    291   {
    292     if ( (0 < num_results) &&
    293          (! GNUNET_TIME_relative_is_zero (timeout)) )
    294       GNUNET_snprintf (url,
    295                        sizeof (url),
    296                        "history/outgoing?limit=%lld&offset=%llu&timeout_ms=%llu",
    297                        (long long) num_results,
    298                        (unsigned long long) start_row,
    299                        tms);
    300     else
    301       GNUNET_snprintf (url,
    302                        sizeof (url),
    303                        "history/outgoing?limit=%lld&offset=%llu",
    304                        (long long) num_results,
    305                        (unsigned long long) start_row);
    306   }
    307   hh = GNUNET_new (struct TALER_BANK_DebitHistoryHandle);
    308   hh->hcb = hres_cb;
    309   hh->hcb_cls = hres_cb_cls;
    310   hh->request_url = TALER_url_join (auth->wire_gateway_url,
    311                                     url,
    312                                     NULL);
    313   if (NULL == hh->request_url)
    314   {
    315     GNUNET_free (hh);
    316     GNUNET_break (0);
    317     return NULL;
    318   }
    319   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    320               "Requesting debit history at `%s'\n",
    321               hh->request_url);
    322   eh = curl_easy_init ();
    323   if ( (NULL == eh) ||
    324        (GNUNET_OK !=
    325         TALER_BANK_setup_auth_ (eh,
    326                                 auth)) ||
    327        (CURLE_OK !=
    328         curl_easy_setopt (eh,
    329                           CURLOPT_URL,
    330                           hh->request_url)) )
    331   {
    332     GNUNET_break (0);
    333     TALER_BANK_debit_history_cancel (hh);
    334     if (NULL != eh)
    335       curl_easy_cleanup (eh);
    336     return NULL;
    337   }
    338   if (0 != tms)
    339   {
    340     GNUNET_break (CURLE_OK ==
    341                   curl_easy_setopt (eh,
    342                                     CURLOPT_TIMEOUT_MS,
    343                                     (long) tms + GRACE_PERIOD_MS));
    344   }
    345   hh->job = GNUNET_CURL_job_add2 (ctx,
    346                                   eh,
    347                                   NULL,
    348                                   &handle_debit_history_finished,
    349                                   hh);
    350   if (NULL == hh->job)
    351   {
    352     GNUNET_break (0);
    353     /* GNUNET_CURL_job_add2() already released @a eh */
    354     TALER_BANK_debit_history_cancel (hh);
    355     return NULL;
    356   }
    357   return hh;
    358 }
    359 
    360 
    361 void
    362 TALER_BANK_debit_history_cancel (struct TALER_BANK_DebitHistoryHandle *hh)
    363 {
    364   if (NULL != hh->job)
    365   {
    366     GNUNET_CURL_job_cancel (hh->job);
    367     hh->job = NULL;
    368   }
    369   GNUNET_free (hh->request_url);
    370   GNUNET_free (hh);
    371 }
    372 
    373 
    374 /* end of bank_api_debit.c */