merchant

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

merchant_api_merchant_get_order.c (15886B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018, 2019, 2020 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_merchant_get_order.c
     19  * @brief Implementation of the GET /private/orders/$ORDER request
     20  * @author Christian Grothoff
     21  * @author Marcello Stanisci
     22  * @author Florian Dold
     23  */
     24 #include "platform.h"
     25 #include <curl/curl.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h> /* just for HTTP status codes */
     28 #include <gnunet/gnunet_util_lib.h>
     29 #include <gnunet/gnunet_curl_lib.h>
     30 #include "taler_merchant_service.h"
     31 #include "merchant_api_curl_defaults.h"
     32 #include <taler/taler_json_lib.h>
     33 #include <taler/taler_signatures.h>
     34 
     35 
     36 /**
     37  * Maximum number of refund details we return.
     38  */
     39 #define MAX_REFUND_DETAILS 1024
     40 
     41 /**
     42  * Maximum number of wire details we return.
     43  */
     44 #define MAX_WIRE_DETAILS 1024
     45 
     46 
     47 /**
     48  * @brief A GET /private/orders/$ORDER handle
     49  */
     50 struct TALER_MERCHANT_OrderMerchantGetHandle
     51 {
     52 
     53   /**
     54    * The url for this request.
     55    */
     56   char *url;
     57 
     58   /**
     59    * Handle for the request.
     60    */
     61   struct GNUNET_CURL_Job *job;
     62 
     63   /**
     64    * Function to call with the result.
     65    */
     66   TALER_MERCHANT_OrderMerchantGetCallback cb;
     67 
     68   /**
     69    * Closure for @a cb.
     70    */
     71   void *cb_cls;
     72 
     73   /**
     74    * Reference to the execution context.
     75    */
     76   struct GNUNET_CURL_Context *ctx;
     77 };
     78 
     79 
     80 /**
     81  * Function called when we're done processing the GET /private/orders/$ORDER
     82  * request and we got an HTTP status of OK and the order was unpaid. Parse
     83  * the response and call the callback.
     84  *
     85  * @param omgh handle for the request
     86  * @param[in,out] osr HTTP response we got
     87  */
     88 static void
     89 handle_unpaid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
     90                struct TALER_MERCHANT_OrderStatusResponse *osr)
     91 {
     92   struct GNUNET_JSON_Specification spec[] = {
     93     TALER_JSON_spec_amount_any (
     94       "total_amount",
     95       &osr->details.ok.details.unpaid.contract_amount),
     96     GNUNET_JSON_spec_mark_optional (
     97       GNUNET_JSON_spec_string (
     98         "already_paid_order_id",
     99         &osr->details.ok.details.unpaid.already_paid_order_id),
    100       NULL),
    101     GNUNET_JSON_spec_string (
    102       "taler_pay_uri",
    103       &osr->details.ok.details.unpaid.taler_pay_uri),
    104     GNUNET_JSON_spec_string (
    105       "summary",
    106       &osr->details.ok.details.unpaid.summary),
    107     GNUNET_JSON_spec_timestamp (
    108       "creation_time",
    109       &osr->details.ok.details.unpaid.creation_time),
    110     GNUNET_JSON_spec_end ()
    111   };
    112 
    113   if (GNUNET_OK !=
    114       GNUNET_JSON_parse (osr->hr.reply,
    115                          spec,
    116                          NULL, NULL))
    117   {
    118     GNUNET_break_op (0);
    119     osr->hr.http_status = 0;
    120     osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    121     omgh->cb (omgh->cb_cls,
    122               osr);
    123     return;
    124   }
    125   osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID;
    126   omgh->cb (omgh->cb_cls,
    127             osr);
    128 }
    129 
    130 
    131 /**
    132  * Function called when we're done processing the GET /private/orders/$ORDER
    133  * request and we got an HTTP status of OK and the order was claimed but not
    134  * paid. Parse the response and call the callback.
    135  *
    136  * @param omgh handle for the request
    137  * @param[in,out] osr HTTP response we got
    138  */
    139 static void
    140 handle_claimed (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
    141                 struct TALER_MERCHANT_OrderStatusResponse *osr)
    142 {
    143   struct GNUNET_JSON_Specification spec[] = {
    144     GNUNET_JSON_spec_object_const (
    145       "contract_terms",
    146       &osr->details.ok.details.claimed.contract_terms),
    147     GNUNET_JSON_spec_end ()
    148   };
    149 
    150   if (GNUNET_OK !=
    151       GNUNET_JSON_parse (osr->hr.reply,
    152                          spec,
    153                          NULL, NULL))
    154   {
    155     GNUNET_break_op (0);
    156     osr->hr.http_status = 0;
    157     osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    158     omgh->cb (omgh->cb_cls,
    159               osr);
    160     return;
    161   }
    162   osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED;
    163   omgh->cb (omgh->cb_cls,
    164             osr);
    165 }
    166 
    167 
    168 /**
    169  * Function called when we're done processing the GET /private/orders/$ORDER
    170  * request and we got an HTTP status of OK and the order was paid. Parse
    171  * the response and call the callback.
    172  *
    173  * @param omgh handle for the request
    174  * @param[in,out] osr HTTP response we got
    175  */
    176 static void
    177 handle_paid (struct TALER_MERCHANT_OrderMerchantGetHandle *omgh,
    178              struct TALER_MERCHANT_OrderStatusResponse *osr)
    179 {
    180   uint32_t hc32;
    181   const json_t *wire_details;
    182   const json_t *refund_details;
    183   struct GNUNET_JSON_Specification spec[] = {
    184     GNUNET_JSON_spec_bool ("refunded",
    185                            &osr->details.ok.details.paid.refunded),
    186     GNUNET_JSON_spec_bool ("refund_pending",
    187                            &osr->details.ok.details.paid.refund_pending),
    188     GNUNET_JSON_spec_bool ("wired",
    189                            &osr->details.ok.details.paid.wired),
    190     TALER_JSON_spec_amount_any ("deposit_total",
    191                                 &osr->details.ok.details.paid.deposit_total),
    192     TALER_JSON_spec_ec ("exchange_code",
    193                         &osr->details.ok.details.paid.exchange_ec),
    194     GNUNET_JSON_spec_uint32 ("exchange_http_status",
    195                              &hc32),
    196     TALER_JSON_spec_amount_any ("refund_amount",
    197                                 &osr->details.ok.details.paid.refund_amount),
    198     GNUNET_JSON_spec_object_const (
    199       "contract_terms",
    200       &osr->details.ok.details.paid.contract_terms),
    201     GNUNET_JSON_spec_array_const ("wire_details",
    202                                   &wire_details),
    203     GNUNET_JSON_spec_array_const ("refund_details",
    204                                   &refund_details),
    205     /* Only available since **v14** */
    206     GNUNET_JSON_spec_mark_optional (
    207       GNUNET_JSON_spec_timestamp ("last_payment",
    208                                   &osr->details.ok.details.paid.last_payment),
    209       NULL),
    210     GNUNET_JSON_spec_end ()
    211   };
    212 
    213   if (GNUNET_OK !=
    214       GNUNET_JSON_parse (osr->hr.reply,
    215                          spec,
    216                          NULL, NULL))
    217   {
    218     GNUNET_break_op (0);
    219     osr->hr.http_status = 0;
    220     osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    221     omgh->cb (omgh->cb_cls,
    222               osr);
    223     return;
    224   }
    225   osr->details.ok.status = TALER_MERCHANT_OSC_PAID;
    226 
    227   osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32;
    228   {
    229     unsigned int wts_len = (unsigned int) json_array_size (wire_details);
    230     unsigned int ref_len = (unsigned int) json_array_size (refund_details);
    231 
    232     if ( (json_array_size (wire_details) != (size_t)  wts_len) ||
    233          (wts_len > MAX_WIRE_DETAILS) )
    234     {
    235       GNUNET_break (0);
    236       osr->hr.http_status = 0;
    237       osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
    238       omgh->cb (omgh->cb_cls,
    239                 osr);
    240       return;
    241     }
    242     if ( (json_array_size (refund_details) != (size_t)  ref_len) ||
    243          (ref_len > MAX_REFUND_DETAILS) )
    244     {
    245       GNUNET_break (0);
    246       osr->hr.http_status = 0;
    247       osr->hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
    248       omgh->cb (omgh->cb_cls,
    249                 osr);
    250       return;
    251     }
    252     {
    253       struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)];
    254       struct TALER_MERCHANT_RefundOrderDetail ref[GNUNET_NZL (ref_len)];
    255 
    256       for (unsigned int i = 0; i<wts_len; i++)
    257       {
    258         struct TALER_MERCHANT_WireTransfer *wt = &wts[i];
    259         const json_t *w = json_array_get (wire_details,
    260                                           i);
    261         struct GNUNET_JSON_Specification ispec[] = {
    262           TALER_JSON_spec_web_url ("exchange_url",
    263                                    &wt->exchange_url),
    264           GNUNET_JSON_spec_fixed_auto ("wtid",
    265                                        &wt->wtid),
    266           GNUNET_JSON_spec_timestamp ("execution_time",
    267                                       &wt->execution_time),
    268           TALER_JSON_spec_amount_any ("amount",
    269                                       &wt->total_amount),
    270           GNUNET_JSON_spec_bool ("confirmed",
    271                                  &wt->confirmed),
    272           GNUNET_JSON_spec_end ()
    273         };
    274 
    275         if (GNUNET_OK !=
    276             GNUNET_JSON_parse (w,
    277                                ispec,
    278                                NULL, NULL))
    279         {
    280           GNUNET_break_op (0);
    281           osr->hr.http_status = 0;
    282           osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    283           omgh->cb (omgh->cb_cls,
    284                     osr);
    285           return;
    286         }
    287       }
    288 
    289       for (unsigned int i = 0; i<ref_len; i++)
    290       {
    291         struct TALER_MERCHANT_RefundOrderDetail *ro = &ref[i];
    292         const json_t *w = json_array_get (refund_details,
    293                                           i);
    294         struct GNUNET_JSON_Specification ispec[] = {
    295           TALER_JSON_spec_amount_any ("amount",
    296                                       &ro->refund_amount),
    297           GNUNET_JSON_spec_string ("reason",
    298                                    &ro->reason),
    299           GNUNET_JSON_spec_timestamp ("timestamp",
    300                                       &ro->refund_time),
    301           GNUNET_JSON_spec_end ()
    302         };
    303 
    304         if (GNUNET_OK !=
    305             GNUNET_JSON_parse (w,
    306                                ispec,
    307                                NULL, NULL))
    308         {
    309           GNUNET_break_op (0);
    310           osr->hr.http_status = 0;
    311           osr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    312           omgh->cb (omgh->cb_cls,
    313                     osr);
    314           return;
    315         }
    316       }
    317 
    318       osr->details.ok.details.paid.wts = wts;
    319       osr->details.ok.details.paid.wts_len = wts_len;
    320       osr->details.ok.details.paid.refunds = ref;
    321       osr->details.ok.details.paid.refunds_len = ref_len;
    322       omgh->cb (omgh->cb_cls,
    323                 osr);
    324     }
    325   }
    326 }
    327 
    328 
    329 /**
    330  * Function called when we're done processing the GET /private/orders/$ORDER
    331  * request.
    332  *
    333  * @param cls the `struct TALER_MERCHANT_OrderMerchantGetHandle`
    334  * @param response_code HTTP response code, 0 on error
    335  * @param response response body, NULL if not in JSON
    336  */
    337 static void
    338 handle_merchant_order_get_finished (void *cls,
    339                                     long response_code,
    340                                     const void *response)
    341 {
    342   struct TALER_MERCHANT_OrderMerchantGetHandle *omgh = cls;
    343   const json_t *json = response;
    344   const char *order_status;
    345   struct TALER_MERCHANT_OrderStatusResponse osr = {
    346     .hr.http_status = (unsigned int) response_code,
    347     .hr.reply = json
    348   };
    349 
    350   omgh->job = NULL;
    351   switch (response_code)
    352   {
    353   case MHD_HTTP_OK:
    354     /* see below */
    355     break;
    356   case MHD_HTTP_ACCEPTED:
    357     /* see below */
    358     omgh->cb (omgh->cb_cls,
    359               &osr);
    360     TALER_MERCHANT_merchant_order_get_cancel (omgh);
    361     return;
    362   case MHD_HTTP_UNAUTHORIZED:
    363     osr.hr.ec = TALER_JSON_get_error_code (json);
    364     osr.hr.hint = TALER_JSON_get_error_hint (json);
    365     omgh->cb (omgh->cb_cls,
    366               &osr);
    367     TALER_MERCHANT_merchant_order_get_cancel (omgh);
    368     return;
    369   case MHD_HTTP_NOT_FOUND:
    370     osr.hr.ec = TALER_JSON_get_error_code (json);
    371     osr.hr.hint = TALER_JSON_get_error_hint (json);
    372     omgh->cb (omgh->cb_cls,
    373               &osr);
    374     TALER_MERCHANT_merchant_order_get_cancel (omgh);
    375     return;
    376   case MHD_HTTP_GATEWAY_TIMEOUT:
    377     osr.hr.ec = TALER_JSON_get_error_code (json);
    378     osr.hr.hint = TALER_JSON_get_error_hint (json);
    379     omgh->cb (omgh->cb_cls,
    380               &osr);
    381     TALER_MERCHANT_merchant_order_get_cancel (omgh);
    382     return;
    383   default:
    384     osr.hr.ec = TALER_JSON_get_error_code (json);
    385     osr.hr.hint = TALER_JSON_get_error_hint (json);
    386     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    387                 "Polling payment failed with HTTP status code %u/%d\n",
    388                 (unsigned int) response_code,
    389                 (int) osr.hr.ec);
    390     GNUNET_break_op (0);
    391     omgh->cb (omgh->cb_cls,
    392               &osr);
    393     TALER_MERCHANT_merchant_order_get_cancel (omgh);
    394     return;
    395   }
    396 
    397   order_status = json_string_value (json_object_get (json,
    398                                                      "order_status"));
    399 
    400   if (NULL == order_status)
    401   {
    402     GNUNET_break_op (0);
    403     osr.hr.http_status = 0;
    404     osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    405     omgh->cb (omgh->cb_cls,
    406               &osr);
    407     TALER_MERCHANT_merchant_order_get_cancel (omgh);
    408     return;
    409   }
    410 
    411   if (0 == strcmp ("paid",
    412                    order_status))
    413   {
    414     handle_paid (omgh,
    415                  &osr);
    416   }
    417   else if (0 == strcmp ("claimed",
    418                         order_status))
    419   {
    420     handle_claimed (omgh,
    421                     &osr);
    422   }
    423   else if (0 == strcmp ("unpaid",
    424                         order_status))
    425   {
    426     handle_unpaid (omgh,
    427                    &osr);
    428   }
    429   else
    430   {
    431     GNUNET_break_op (0);
    432     osr.hr.http_status = 0;
    433     osr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    434     omgh->cb (omgh->cb_cls,
    435               &osr);
    436   }
    437   TALER_MERCHANT_merchant_order_get_cancel (omgh);
    438 }
    439 
    440 
    441 struct TALER_MERCHANT_OrderMerchantGetHandle *
    442 TALER_MERCHANT_merchant_order_get (
    443   struct GNUNET_CURL_Context *ctx,
    444   const char *backend_url,
    445   const char *order_id,
    446   const char *session_id,
    447   struct GNUNET_TIME_Relative timeout,
    448   TALER_MERCHANT_OrderMerchantGetCallback cb,
    449   void *cb_cls)
    450 {
    451   struct TALER_MERCHANT_OrderMerchantGetHandle *omgh;
    452   unsigned int tms;
    453 
    454   tms = (unsigned int) (timeout.rel_value_us
    455                         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    456   omgh = GNUNET_new (struct TALER_MERCHANT_OrderMerchantGetHandle);
    457   omgh->ctx = ctx;
    458   omgh->cb = cb;
    459   omgh->cb_cls = cb_cls;
    460   {
    461     char *path;
    462     char timeout_ms[32];
    463 
    464     GNUNET_snprintf (timeout_ms,
    465                      sizeof (timeout_ms),
    466                      "%u",
    467                      tms);
    468     GNUNET_asprintf (&path,
    469                      "private/orders/%s",
    470                      order_id);
    471     omgh->url = TALER_url_join (backend_url,
    472                                 path,
    473                                 "session_id", session_id,
    474                                 "timeout_ms", (0 != tms) ? timeout_ms : NULL,
    475                                 NULL);
    476     GNUNET_free (path);
    477   }
    478   if (NULL == omgh->url)
    479   {
    480     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    481                 "Could not construct request URL.\n");
    482     GNUNET_free (omgh);
    483     return NULL;
    484   }
    485 
    486   {
    487     CURL *eh;
    488 
    489     eh = TALER_MERCHANT_curl_easy_get_ (omgh->url);
    490     if (NULL == eh)
    491     {
    492       GNUNET_break (0);
    493       GNUNET_free (omgh->url);
    494       GNUNET_free (omgh);
    495       return NULL;
    496     }
    497     if (0 != tms)
    498     {
    499       GNUNET_break (CURLE_OK ==
    500                     curl_easy_setopt (eh,
    501                                       CURLOPT_TIMEOUT_MS,
    502                                       (long) (tms + 100L)));
    503     }
    504 
    505     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    506                 "Getting order status from %s\n",
    507                 omgh->url);
    508     if (NULL == (omgh->job =
    509                    GNUNET_CURL_job_add (ctx,
    510                                         eh,
    511                                         &handle_merchant_order_get_finished,
    512                                         omgh)))
    513     {
    514       GNUNET_break (0);
    515       GNUNET_free (omgh->url);
    516       GNUNET_free (omgh);
    517       return NULL;
    518     }
    519   }
    520   return omgh;
    521 }
    522 
    523 
    524 void
    525 TALER_MERCHANT_merchant_order_get_cancel (
    526   struct TALER_MERCHANT_OrderMerchantGetHandle *omgh)
    527 {
    528   if (NULL != omgh->job)
    529   {
    530     GNUNET_CURL_job_cancel (omgh->job);
    531     omgh->job = NULL;
    532   }
    533   GNUNET_free (omgh->url);
    534   GNUNET_free (omgh);
    535 }
    536 
    537 
    538 /* end of merchant_api_merchant_get_order.c */