merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

merchant_api_common.c (16834B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Lesser General Public License as published by the Free Software
      7   Foundation; either version 2.1, 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 Lesser General Public License for more details.
     12 
     13   You should have received a copy of the GNU Lesser General Public License along with
     14   TALER; see the file COPYING.LGPL.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file merchant_api_common.c
     19  * @brief Implementation of common logic for libtalermerchant
     20  * @author Christian Grothoff
     21  * @author Priscilla Huang
     22  */
     23 #include "platform.h"
     24 #include "microhttpd.h"
     25 #include <curl/curl.h>
     26 #include "taler_merchant_service.h"
     27 #include "merchant_api_common.h"
     28 #include <gnunet/gnunet_uri_lib.h>
     29 #include <taler/taler_json_lib.h>
     30 #include "taler_merchant_util.h"
     31 
     32 
     33 static void
     34 TALER_MERCHANT_format_fractional_string (uint64_t integer,
     35                                          uint32_t fractional,
     36                                          char *buffer,
     37                                          size_t buffer_length)
     38 {
     39   GNUNET_assert (NULL != buffer);
     40   GNUNET_assert (0 < buffer_length);
     41   GNUNET_assert (fractional < MERCHANT_UNIT_FRAC_BASE);
     42 
     43   if (0 == fractional)
     44   {
     45     GNUNET_snprintf (buffer,
     46                      buffer_length,
     47                      "%lu",
     48                      integer);
     49     return;
     50   }
     51   {
     52     char frac_buf[MERCHANT_UNIT_FRAC_MAX_DIGITS + 1];
     53     size_t idx;
     54 
     55     GNUNET_snprintf (frac_buf,
     56                      sizeof (frac_buf),
     57                      "%0*u",
     58                      MERCHANT_UNIT_FRAC_MAX_DIGITS,
     59                      (unsigned int) fractional);
     60     for (idx = strlen (frac_buf); idx > 0; idx--)
     61     {
     62       if ('0' != frac_buf[idx - 1])
     63         break;
     64       frac_buf[idx - 1] = '\0';
     65     }
     66     if ('\0' == frac_buf[0])
     67       GNUNET_strlcpy (frac_buf,
     68                       "0",
     69                       sizeof (frac_buf));
     70     GNUNET_snprintf (buffer,
     71                      buffer_length,
     72                      "%lu.%s",
     73                      integer,
     74                      frac_buf);
     75   }
     76 }
     77 
     78 
     79 void
     80 TALER_MERCHANT_format_quantity_string (uint64_t quantity,
     81                                        uint32_t quantity_frac,
     82                                        char *buffer,
     83                                        size_t buffer_length)
     84 {
     85   TALER_MERCHANT_format_fractional_string (quantity,
     86                                            quantity_frac,
     87                                            buffer,
     88                                            buffer_length);
     89 }
     90 
     91 
     92 void
     93 TALER_MERCHANT_format_stock_string (uint64_t total_stock,
     94                                     uint32_t total_stock_frac,
     95                                     char *buffer,
     96                                     size_t buffer_length)
     97 {
     98   if ( (INT64_MAX == (int64_t) total_stock) &&
     99        (INT32_MAX == (int32_t) total_stock_frac) )
    100   {
    101     GNUNET_snprintf (buffer,
    102                      buffer_length,
    103                      "-1");
    104     return;
    105   }
    106   TALER_MERCHANT_format_fractional_string (total_stock,
    107                                            total_stock_frac,
    108                                            buffer,
    109                                            buffer_length);
    110 }
    111 
    112 
    113 void
    114 TALER_MERCHANT_parse_error_details_ (const json_t *response,
    115                                      unsigned int http_status,
    116                                      struct TALER_MERCHANT_HttpResponse *hr)
    117 {
    118   const json_t *jc;
    119 
    120   memset (hr, 0, sizeof (*hr));
    121   hr->reply = response;
    122   hr->http_status = http_status;
    123   if (NULL == response)
    124   {
    125     hr->ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    126     return;
    127   }
    128   hr->ec = TALER_JSON_get_error_code (response);
    129   hr->hint = TALER_JSON_get_error_hint (response);
    130 
    131   /* handle 'exchange_http_status' */
    132   jc = json_object_get (response,
    133                         "exchange_http_status");
    134   /* The caller already knows that the JSON represents an error,
    135      so we are dealing with a missing error code here.  */
    136   if (NULL == jc)
    137     return; /* no need to bother with exchange_code/hint if we had no status */
    138   if (! json_is_integer (jc))
    139   {
    140     GNUNET_break_op (0);
    141     return;
    142   }
    143   hr->exchange_http_status = (unsigned int) json_integer_value (jc);
    144 
    145   /* handle 'exchange_reply' */
    146   jc = json_object_get (response,
    147                         "exchange_reply");
    148   if (! json_is_object (jc))
    149   {
    150     GNUNET_break_op (0);
    151   }
    152   else
    153   {
    154     hr->exchange_reply = jc;
    155   }
    156 
    157   /* handle 'exchange_code' */
    158   jc = json_object_get (response,
    159                         "exchange_code");
    160   /* The caller already knows that the JSON represents an error,
    161      so we are dealing with a missing error code here.  */
    162   if (NULL == jc)
    163     return; /* no need to bother with exchange-hint if we had no code */
    164   if (! json_is_integer (jc))
    165   {
    166     GNUNET_break_op (0);
    167     hr->exchange_code = TALER_EC_INVALID;
    168   }
    169   else
    170   {
    171     hr->exchange_code = (enum TALER_ErrorCode) (int) json_integer_value (jc);
    172   }
    173 
    174   /* handle 'exchange-hint' */
    175   jc = json_object_get (response,
    176                         "exchange-hint");
    177   /* The caller already knows that the JSON represents an error,
    178      so we are dealing with a missing error code here.  */
    179   if (NULL == jc)
    180     return;
    181   if (! json_is_string (jc))
    182   {
    183     GNUNET_break_op (0);
    184   }
    185   else
    186   {
    187     hr->exchange_hint = json_string_value (jc);
    188   }
    189 }
    190 
    191 
    192 enum GNUNET_GenericReturnValue
    193 TALER_MERCHANT_parse_pay_uri (const char *pay_uri,
    194                               struct TALER_MERCHANT_PayUriData *parse_data)
    195 {
    196   char *cp = GNUNET_strdup (pay_uri);
    197   struct GNUNET_Uri u;
    198 
    199   if (0 !=
    200       GNUNET_uri_parse (&u,
    201                         cp))
    202   {
    203     GNUNET_free (cp);
    204     GNUNET_break_op (0);
    205     return GNUNET_SYSERR;
    206   }
    207   if ((0 != strcasecmp ("taler",
    208                         u.scheme)) &&
    209       (0 != strcasecmp ("taler+http",
    210                         u.scheme)))
    211   {
    212     fprintf (stderr,
    213              "Bad schema %s\n",
    214              u.scheme);
    215     GNUNET_free (cp);
    216     GNUNET_break_op (0);
    217     return GNUNET_SYSERR;
    218   }
    219   parse_data->use_http = (0 == strcasecmp ("taler+http",
    220                                            u.scheme));
    221   if (0 != strcasecmp ("pay",
    222                        u.host))
    223   {
    224     GNUNET_break_op (0);
    225     GNUNET_free (cp);
    226     return GNUNET_SYSERR;
    227   }
    228 
    229   {
    230     char *order_id;
    231     char *mpp;
    232     char *session_id = strrchr (u.path,
    233                                 '/');
    234     struct TALER_ClaimTokenP *claim_token = NULL;
    235 
    236     if (NULL == session_id)
    237     {
    238       GNUNET_break_op (0);
    239       GNUNET_free (cp);
    240       return GNUNET_SYSERR;
    241     }
    242     *session_id = '\0';
    243     ++session_id;
    244 
    245     order_id = strrchr (u.path,
    246                         '/');
    247     if (NULL == order_id)
    248     {
    249       GNUNET_break_op (0);
    250       GNUNET_free (cp);
    251       return GNUNET_SYSERR;
    252     }
    253     *order_id = '\0';
    254     ++order_id;
    255     mpp = strchr (u.path,
    256                   '/');
    257     if (NULL != mpp)
    258     {
    259       *mpp = '\0';
    260       ++mpp;
    261     }
    262 
    263     {
    264       char *ct_str = u.query;
    265       char *ct_data;
    266 
    267       if (NULL != ct_str)
    268       {
    269         ct_data = strchr (u.query,
    270                           '=');
    271         if (NULL == ct_data)
    272         {
    273           GNUNET_break_op (0);
    274           GNUNET_free (cp);
    275           return GNUNET_SYSERR;
    276         }
    277         *ct_data = '\0';
    278         ++ct_data;
    279         claim_token = GNUNET_new (struct TALER_ClaimTokenP);
    280         if ( (0 != strcmp ("c",
    281                            u.query)) ||
    282              (GNUNET_OK !=
    283               GNUNET_STRINGS_string_to_data (ct_data,
    284                                              strlen (ct_data),
    285                                              claim_token,
    286                                              sizeof (*claim_token))) )
    287         {
    288           GNUNET_break_op (0);
    289           GNUNET_free (claim_token);
    290           GNUNET_free (cp);
    291           return GNUNET_SYSERR;
    292         }
    293       }
    294     }
    295 
    296     parse_data->merchant_prefix_path
    297       = (NULL == mpp)
    298         ? NULL
    299         : GNUNET_strdup (mpp);
    300     parse_data->merchant_host = GNUNET_strdup (u.path);
    301     parse_data->order_id = GNUNET_strdup (order_id);
    302     parse_data->session_id
    303       = (0 < strlen (session_id))
    304         ? GNUNET_strdup (session_id)
    305         : NULL;
    306     parse_data->claim_token = claim_token;
    307     parse_data->ssid
    308       = (NULL == u.fragment)
    309         ? NULL
    310         : GNUNET_strdup (u.fragment);
    311   }
    312   GNUNET_free (cp);
    313   return GNUNET_OK;
    314 }
    315 
    316 
    317 void
    318 TALER_MERCHANT_parse_pay_uri_free (
    319   struct TALER_MERCHANT_PayUriData *parse_data)
    320 {
    321   GNUNET_free (parse_data->merchant_host);
    322   GNUNET_free (parse_data->merchant_prefix_path);
    323   GNUNET_free (parse_data->order_id);
    324   GNUNET_free (parse_data->session_id);
    325   GNUNET_free (parse_data->claim_token);
    326   GNUNET_free (parse_data->ssid);
    327 }
    328 
    329 
    330 enum GNUNET_GenericReturnValue
    331 TALER_MERCHANT_parse_refund_uri (
    332   const char *refund_uri,
    333   struct TALER_MERCHANT_RefundUriData *parse_data)
    334 {
    335   char *cp = GNUNET_strdup (refund_uri);
    336   struct GNUNET_Uri u;
    337 
    338   if (0 !=
    339       GNUNET_uri_parse (&u,
    340                         cp))
    341   {
    342     GNUNET_free (cp);
    343     GNUNET_break_op (0);
    344     return GNUNET_SYSERR;
    345   }
    346   if ((0 != strcasecmp ("taler",
    347                         u.scheme)) &&
    348       (0 != strcasecmp ("taler+http",
    349                         u.scheme)))
    350   {
    351     GNUNET_free (cp);
    352     GNUNET_break_op (0);
    353     return GNUNET_SYSERR;
    354   }
    355   parse_data->use_http = (0 == strcasecmp ("taler+http",
    356                                            u.scheme));
    357 
    358   if (0 != strcasecmp ("refund",
    359                        u.host))
    360   {
    361     GNUNET_break_op (0);
    362     GNUNET_free (cp);
    363     return GNUNET_SYSERR;
    364   }
    365 
    366   {
    367     char *order_id;
    368     char *last_seg = strrchr (u.path,
    369                               '/');
    370 
    371     if (NULL == last_seg)
    372     {
    373       GNUNET_break_op (0);
    374       GNUNET_free (cp);
    375       return GNUNET_SYSERR;
    376     }
    377     *last_seg = '\0';
    378     ++last_seg;
    379 
    380     order_id = strrchr (u.path,
    381                         '/');
    382     if (NULL == order_id)
    383     {
    384       GNUNET_break_op (0);
    385       GNUNET_free (cp);
    386       return GNUNET_SYSERR;
    387     }
    388     *order_id = '\0';
    389     ++order_id;
    390     if (0 != strlen (last_seg))
    391     {
    392       GNUNET_break_op (0);
    393       GNUNET_free (cp);
    394       return GNUNET_SYSERR;
    395     }
    396 
    397     {
    398       char *mpp;
    399 
    400       mpp = strchr (u.path,
    401                     '/');
    402       if (NULL != mpp)
    403       {
    404         *mpp = '\0';
    405         ++mpp;
    406       }
    407 
    408       parse_data->merchant_prefix_path
    409         = (NULL == mpp)
    410           ? NULL
    411           : GNUNET_strdup (mpp);
    412     }
    413     parse_data->merchant_host = GNUNET_strdup (u.path);
    414     parse_data->order_id = GNUNET_strdup (order_id);
    415     parse_data->ssid
    416       = (NULL == u.fragment)
    417         ? NULL
    418         : GNUNET_strdup (u.fragment);
    419   }
    420   GNUNET_free (cp);
    421   return GNUNET_OK;
    422 }
    423 
    424 
    425 void
    426 TALER_MERCHANT_parse_refund_uri_free (
    427   struct TALER_MERCHANT_RefundUriData *parse_data)
    428 {
    429   GNUNET_free (parse_data->merchant_host);
    430   GNUNET_free (parse_data->merchant_prefix_path);
    431   GNUNET_free (parse_data->order_id);
    432   GNUNET_free (parse_data->ssid);
    433 }
    434 
    435 
    436 void
    437 TALER_MERCHANT_handle_order_creation_response_ (
    438   TALER_MERCHANT_PostOrdersCallback cb,
    439   void *cb_cls,
    440   long response_code,
    441   const json_t *json)
    442 {
    443   struct TALER_MERCHANT_PostOrdersReply por = {
    444     .hr.http_status = (unsigned int) response_code,
    445     .hr.reply = json
    446   };
    447   struct TALER_ClaimTokenP token;
    448 
    449   switch (response_code)
    450   {
    451   case 0:
    452     por.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    453     break;
    454   case MHD_HTTP_OK:
    455     {
    456       bool no_token;
    457       bool no_pay_deadline;
    458       struct GNUNET_JSON_Specification spec[] = {
    459         GNUNET_JSON_spec_string ("order_id",
    460                                  &por.details.ok.order_id),
    461         GNUNET_JSON_spec_mark_optional (
    462           GNUNET_JSON_spec_fixed_auto ("token",
    463                                        &token),
    464           &no_token),
    465         GNUNET_JSON_spec_mark_optional (
    466           /* optional for pre-v21 compatibility */
    467           GNUNET_JSON_spec_timestamp ("pay_deadline",
    468                                       &por.details.ok.pay_deadline),
    469           &no_pay_deadline),
    470         GNUNET_JSON_spec_end ()
    471       };
    472 
    473       if (GNUNET_OK !=
    474           GNUNET_JSON_parse (json,
    475                              spec,
    476                              NULL, NULL))
    477       {
    478         GNUNET_break_op (0);
    479         por.hr.http_status = 0;
    480         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    481       }
    482       else
    483       {
    484         if (! no_token)
    485           por.details.ok.token = &token;
    486         if (no_pay_deadline)
    487         {
    488           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    489                       "Talking to outdated merchant backend, payment deadline unavailable.\n");
    490           por.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
    491         }
    492       }
    493     }
    494     break;
    495   case MHD_HTTP_BAD_REQUEST:
    496     json_dumpf (json,
    497                 stderr,
    498                 JSON_INDENT (2));
    499     por.hr.ec = TALER_JSON_get_error_code (json);
    500     por.hr.hint = TALER_JSON_get_error_hint (json);
    501     /* This should never happen, either us or
    502        the merchant is buggy (or API version conflict);
    503        just pass JSON reply to the application */
    504     break;
    505   case MHD_HTTP_UNAUTHORIZED:
    506     por.hr.ec = TALER_JSON_get_error_code (json);
    507     por.hr.hint = TALER_JSON_get_error_hint (json);
    508     /* Nothing really to verify, merchant says we need to authenticate. */
    509     break;
    510   case MHD_HTTP_FORBIDDEN:
    511     /* Nothing really to verify, merchant says one
    512        of the signatures is invalid; as we checked them,
    513        this should never happen, we should pass the JSON
    514        reply to the application */
    515     por.hr.ec = TALER_JSON_get_error_code (json);
    516     por.hr.hint = TALER_JSON_get_error_hint (json);
    517     break;
    518   case MHD_HTTP_NOT_FOUND:
    519     /* Nothing really to verify, this should never
    520        happen, we should pass the JSON reply to the application */
    521     por.hr.ec = TALER_JSON_get_error_code (json);
    522     por.hr.hint = TALER_JSON_get_error_hint (json);
    523     break;
    524   case MHD_HTTP_CONFLICT:
    525     por.hr.ec = TALER_JSON_get_error_code (json);
    526     por.hr.hint = TALER_JSON_get_error_hint (json);
    527     break;
    528   case MHD_HTTP_GONE:
    529     /* The quantity of some product requested was not available. */
    530     {
    531       bool rq_frac_missing;
    532       bool aq_frac_missing;
    533 
    534       struct GNUNET_JSON_Specification spec[] = {
    535         GNUNET_JSON_spec_string (
    536           "product_id",
    537           &por.details.gone.product_id),
    538         GNUNET_JSON_spec_uint64 (
    539           "requested_quantity",
    540           &por.details.gone.requested_quantity),
    541         GNUNET_JSON_spec_mark_optional (
    542           GNUNET_JSON_spec_uint32 (
    543             "requested_quantity_frac",
    544             &por.details.gone.requested_quantity_frac),
    545           &rq_frac_missing),
    546         GNUNET_JSON_spec_uint64 (
    547           "available_quantity",
    548           &por.details.gone.available_quantity),
    549         GNUNET_JSON_spec_mark_optional (
    550           GNUNET_JSON_spec_uint32 (
    551             "available_quantity_frac",
    552             &por.details.gone.available_quantity_frac),
    553           &aq_frac_missing),
    554         GNUNET_JSON_spec_mark_optional (
    555           GNUNET_JSON_spec_timestamp (
    556             "restock_expected",
    557             &por.details.gone.restock_expected),
    558           NULL),
    559         GNUNET_JSON_spec_end ()
    560       };
    561 
    562       if (GNUNET_OK !=
    563           GNUNET_JSON_parse (json,
    564                              spec,
    565                              NULL, NULL))
    566       {
    567         GNUNET_break_op (0);
    568         por.hr.http_status = 0;
    569         por.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    570       }
    571       else
    572       {
    573         if (rq_frac_missing)
    574           por.details.gone.requested_quantity_frac = 0;
    575         if (aq_frac_missing)
    576           por.details.gone.available_quantity_frac = 0;
    577       }
    578       break;
    579     }
    580   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    581     /* Order total too high for legal reasons */
    582     por.hr.ec = TALER_JSON_get_error_code (json);
    583     por.hr.hint = TALER_JSON_get_error_hint (json);
    584     break;
    585   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    586     /* Server had an internal issue; we should retry,
    587        but this API leaves this to the application */
    588     por.hr.ec = TALER_JSON_get_error_code (json);
    589     por.hr.hint = TALER_JSON_get_error_hint (json);
    590     break;
    591   default:
    592     /* unexpected response code */
    593     por.hr.ec = TALER_JSON_get_error_code (json);
    594     por.hr.hint = TALER_JSON_get_error_hint (json);
    595     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    596                 "Unexpected response code %u/%d\n",
    597                 (unsigned int) response_code,
    598                 (int) por.hr.ec);
    599     GNUNET_break_op (0);
    600     break;
    601   } // end of switch
    602   cb (cb_cls,
    603       &por);
    604 }