merchant

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

merchant_api_get-orders-ORDER_ID.c (13265B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2018-2026 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_get-orders-ORDER_ID-new.c
     19  * @brief Implementation of the GET /orders/$ORDER_ID request (wallet-facing)
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <curl/curl.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include <taler/taler-merchant/get-orders-ORDER_ID.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include "merchant_api_common.h"
     31 #include <taler/taler_json_lib.h>
     32 
     33 
     34 /**
     35  * Handle for a GET /orders/$ORDER_ID operation (wallet-facing).
     36  */
     37 struct TALER_MERCHANT_GetOrdersHandle
     38 {
     39   /**
     40    * Base URL of the merchant backend.
     41    */
     42   char *base_url;
     43 
     44   /**
     45    * The full URL for this request.
     46    */
     47   char *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_MERCHANT_GetOrdersCallback cb;
     58 
     59   /**
     60    * Closure for @a cb.
     61    */
     62   TALER_MERCHANT_GET_ORDERS_RESULT_CLOSURE *cb_cls;
     63 
     64   /**
     65    * Reference to the execution context.
     66    */
     67   struct GNUNET_CURL_Context *ctx;
     68 
     69   /**
     70    * Order ID.
     71    */
     72   char *order_id;
     73 
     74   /**
     75    * Hash of the contract terms (for authentication).
     76    */
     77   struct TALER_PrivateContractHashP h_contract;
     78 
     79   /**
     80    * Claim token for unclaimed order authentication.
     81    */
     82   struct TALER_ClaimTokenP token;
     83 
     84   /**
     85    * Session ID for repurchase detection, or NULL.
     86    */
     87   char *session_id;
     88 
     89   /**
     90    * Long polling timeout.
     91    */
     92   struct GNUNET_TIME_Relative timeout;
     93 
     94   /**
     95    * Minimum refund amount to wait for, or NULL if unset.
     96    */
     97   struct TALER_Amount min_refund;
     98 
     99   /**
    100    * Whether refunded orders count for repurchase detection.
    101    */
    102   enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;
    103 
    104   /**
    105    * True if @e h_contract was set.
    106    */
    107   bool have_h_contract;
    108 
    109   /**
    110    * True if @e token was set.
    111    */
    112   bool have_token;
    113 
    114   /**
    115    * True if @e min_refund was set.
    116    */
    117   bool have_min_refund;
    118 
    119   /**
    120    * If true, wait until refund is confirmed obtained.
    121    */
    122   bool await_refund_obtained;
    123 };
    124 
    125 
    126 /**
    127  * Function called when we're done processing the
    128  * HTTP GET /orders/$ORDER_ID request (wallet-facing).
    129  *
    130  * @param cls the `struct TALER_MERCHANT_GetOrdersHandle`
    131  * @param response_code HTTP response code, 0 on error
    132  * @param response response body, NULL if not in JSON
    133  */
    134 static void
    135 handle_get_order_finished (void *cls,
    136                            long response_code,
    137                            const void *response)
    138 {
    139   struct TALER_MERCHANT_GetOrdersHandle *oph = cls;
    140   const json_t *json = response;
    141   struct TALER_MERCHANT_GetOrdersResponse owgr = {
    142     .hr.http_status = (unsigned int) response_code,
    143     .hr.reply = json
    144   };
    145 
    146   oph->job = NULL;
    147   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    148               "Got /orders/$ORDER_ID response with status code %u\n",
    149               (unsigned int) response_code);
    150   switch (response_code)
    151   {
    152   case MHD_HTTP_OK:
    153     {
    154       struct GNUNET_JSON_Specification spec[] = {
    155         GNUNET_JSON_spec_bool ("refunded",
    156                                &owgr.details.ok.refunded),
    157         GNUNET_JSON_spec_bool ("refund_pending",
    158                                &owgr.details.ok.refund_pending),
    159         TALER_JSON_spec_amount_any ("refund_amount",
    160                                     &owgr.details.ok.refund_amount),
    161         GNUNET_JSON_spec_mark_optional (
    162           TALER_JSON_spec_amount_any ("refund_taken",
    163                                       &owgr.details.ok.refund_taken),
    164           NULL),
    165         GNUNET_JSON_spec_end ()
    166       };
    167 
    168       if (GNUNET_OK !=
    169           GNUNET_JSON_parse (json,
    170                              spec,
    171                              NULL, NULL))
    172       {
    173         owgr.hr.http_status = 0;
    174         owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    175         break;
    176       }
    177       oph->cb (oph->cb_cls,
    178                &owgr);
    179       TALER_MERCHANT_get_orders_cancel (oph);
    180       return;
    181     }
    182   case MHD_HTTP_ACCEPTED:
    183     {
    184       struct GNUNET_JSON_Specification spec[] = {
    185         GNUNET_JSON_spec_string (
    186           "public_reorder_url",
    187           &owgr.details.accepted.public_reorder_url),
    188         GNUNET_JSON_spec_end ()
    189       };
    190 
    191       if (GNUNET_OK !=
    192           GNUNET_JSON_parse (json,
    193                              spec,
    194                              NULL, NULL))
    195       {
    196         owgr.hr.http_status = 0;
    197         owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    198         break;
    199       }
    200       oph->cb (oph->cb_cls,
    201                &owgr);
    202       TALER_MERCHANT_get_orders_cancel (oph);
    203       return;
    204     }
    205   case MHD_HTTP_FOUND:
    206     /* Redirect; Location header has the target URL.
    207        No JSON body expected. */
    208     break;
    209   case MHD_HTTP_PAYMENT_REQUIRED:
    210     {
    211       struct GNUNET_JSON_Specification spec[] = {
    212         GNUNET_JSON_spec_string (
    213           "taler_pay_uri",
    214           &owgr.details.payment_required.taler_pay_uri),
    215         GNUNET_JSON_spec_mark_optional (
    216           GNUNET_JSON_spec_string (
    217             "already_paid_order_id",
    218             &owgr.details.payment_required.already_paid_order_id),
    219           NULL),
    220         GNUNET_JSON_spec_mark_optional (
    221           GNUNET_JSON_spec_string (
    222             "fulfillment_url",
    223             &owgr.details.payment_required.fulfillment_url),
    224           NULL),
    225         GNUNET_JSON_spec_end ()
    226       };
    227 
    228       if (GNUNET_OK !=
    229           GNUNET_JSON_parse (json,
    230                              spec,
    231                              NULL, NULL))
    232       {
    233         owgr.hr.http_status = 0;
    234         owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    235         break;
    236       }
    237       oph->cb (oph->cb_cls,
    238                &owgr);
    239       TALER_MERCHANT_get_orders_cancel (oph);
    240       return;
    241     }
    242   case MHD_HTTP_BAD_REQUEST:
    243     owgr.hr.ec = TALER_JSON_get_error_code (json);
    244     owgr.hr.hint = TALER_JSON_get_error_hint (json);
    245     break;
    246   case MHD_HTTP_FORBIDDEN:
    247     owgr.hr.ec = TALER_JSON_get_error_code (json);
    248     owgr.hr.hint = TALER_JSON_get_error_hint (json);
    249     break;
    250   case MHD_HTTP_NOT_FOUND:
    251     owgr.hr.ec = TALER_JSON_get_error_code (json);
    252     owgr.hr.hint = TALER_JSON_get_error_hint (json);
    253     break;
    254   case MHD_HTTP_NOT_ACCEPTABLE:
    255     owgr.hr.ec = TALER_JSON_get_error_code (json);
    256     owgr.hr.hint = TALER_JSON_get_error_hint (json);
    257     break;
    258   case MHD_HTTP_CONFLICT:
    259     owgr.hr.ec = TALER_JSON_get_error_code (json);
    260     owgr.hr.hint = TALER_JSON_get_error_hint (json);
    261     break;
    262   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    263     owgr.hr.ec = TALER_JSON_get_error_code (json);
    264     owgr.hr.hint = TALER_JSON_get_error_hint (json);
    265     break;
    266   default:
    267     TALER_MERCHANT_parse_error_details_ (json,
    268                                          response_code,
    269                                          &owgr.hr);
    270     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    271                 "Unexpected response code %u/%d\n",
    272                 (unsigned int) response_code,
    273                 (int) owgr.hr.ec);
    274     break;
    275   }
    276   oph->cb (oph->cb_cls,
    277            &owgr);
    278   TALER_MERCHANT_get_orders_cancel (oph);
    279 }
    280 
    281 
    282 struct TALER_MERCHANT_GetOrdersHandle *
    283 TALER_MERCHANT_get_orders_create (
    284   struct GNUNET_CURL_Context *ctx,
    285   const char *url,
    286   const char *order_id)
    287 {
    288   struct TALER_MERCHANT_GetOrdersHandle *oph;
    289 
    290   oph = GNUNET_new (struct TALER_MERCHANT_GetOrdersHandle);
    291   oph->ctx = ctx;
    292   oph->base_url = GNUNET_strdup (url);
    293   oph->order_id = GNUNET_strdup (order_id);
    294   oph->allow_refunded_for_repurchase = TALER_EXCHANGE_YNA_NO;
    295   return oph;
    296 }
    297 
    298 
    299 enum GNUNET_GenericReturnValue
    300 TALER_MERCHANT_get_orders_set_options_ (
    301   struct TALER_MERCHANT_GetOrdersHandle *oph,
    302   unsigned int num_options,
    303   const struct TALER_MERCHANT_GetOrdersOptionValue *options)
    304 {
    305   for (unsigned int i = 0; i < num_options; i++)
    306   {
    307     const struct TALER_MERCHANT_GetOrdersOptionValue *opt =
    308       &options[i];
    309 
    310     switch (opt->option)
    311     {
    312     case TALER_MERCHANT_GET_ORDERS_OPTION_END:
    313       return GNUNET_OK;
    314     case TALER_MERCHANT_GET_ORDERS_OPTION_TIMEOUT:
    315       oph->timeout = opt->details.timeout;
    316       break;
    317     case TALER_MERCHANT_GET_ORDERS_OPTION_SESSION_ID:
    318       GNUNET_free (oph->session_id);
    319       if (NULL != opt->details.session_id)
    320         oph->session_id = GNUNET_strdup (opt->details.session_id);
    321       break;
    322     case TALER_MERCHANT_GET_ORDERS_OPTION_MIN_REFUND:
    323       oph->min_refund = opt->details.min_refund;
    324       oph->have_min_refund = true;
    325       break;
    326     case TALER_MERCHANT_GET_ORDERS_OPTION_AWAIT_REFUND_OBTAINED:
    327       oph->await_refund_obtained = opt->details.await_refund_obtained;
    328       break;
    329     case TALER_MERCHANT_GET_ORDERS_OPTION_H_CONTRACT:
    330       oph->h_contract = opt->details.h_contract;
    331       oph->have_h_contract = true;
    332       break;
    333     case TALER_MERCHANT_GET_ORDERS_OPTION_TOKEN:
    334       oph->token = opt->details.token;
    335       oph->have_token = true;
    336       break;
    337     case TALER_MERCHANT_GET_ORDERS_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE:
    338       oph->allow_refunded_for_repurchase
    339         = opt->details.allow_refunded_for_repurchase;
    340       break;
    341     default:
    342       GNUNET_break (0);
    343       return GNUNET_NO;
    344     }
    345   }
    346   return GNUNET_OK;
    347 }
    348 
    349 
    350 enum TALER_ErrorCode
    351 TALER_MERCHANT_get_orders_start (
    352   struct TALER_MERCHANT_GetOrdersHandle *oph,
    353   TALER_MERCHANT_GetOrdersCallback cb,
    354   TALER_MERCHANT_GET_ORDERS_RESULT_CLOSURE *cb_cls)
    355 {
    356   CURL *eh;
    357   unsigned int tms;
    358 
    359   oph->cb = cb;
    360   oph->cb_cls = cb_cls;
    361   tms = (unsigned int) (oph->timeout.rel_value_us
    362                         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    363   {
    364     struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s;
    365     char *token_s = NULL;
    366     char *path;
    367     char timeout_ms[32];
    368     const char *yna_s;
    369 
    370     if (oph->have_h_contract)
    371       GNUNET_CRYPTO_hash_to_enc (&oph->h_contract.hash,
    372                                  &h_contract_s);
    373     if (oph->have_token)
    374       token_s = GNUNET_STRINGS_data_to_string_alloc (
    375         &oph->token,
    376         sizeof (oph->token));
    377     GNUNET_snprintf (timeout_ms,
    378                      sizeof (timeout_ms),
    379                      "%u",
    380                      tms);
    381     GNUNET_asprintf (&path,
    382                      "orders/%s",
    383                      oph->order_id);
    384     yna_s = (TALER_EXCHANGE_YNA_NO != oph->allow_refunded_for_repurchase)
    385             ? TALER_yna_to_string (oph->allow_refunded_for_repurchase)
    386             : NULL;
    387     oph->url = TALER_url_join (oph->base_url,
    388                                path,
    389                                "h_contract",
    390                                oph->have_h_contract
    391                                ? h_contract_s.encoding
    392                                : NULL,
    393                                "token",
    394                                token_s,
    395                                "session_id",
    396                                oph->session_id,
    397                                "timeout_ms",
    398                                (0 != tms)
    399                                ? timeout_ms
    400                                : NULL,
    401                                "refund",
    402                                oph->have_min_refund
    403                                ? TALER_amount2s (&oph->min_refund)
    404                                : NULL,
    405                                "await_refund_obtained",
    406                                oph->await_refund_obtained
    407                                ? "yes"
    408                                : NULL,
    409                                "allow_refunded_for_repurchase",
    410                                yna_s,
    411                                NULL);
    412     GNUNET_free (path);
    413     GNUNET_free (token_s);
    414   }
    415   if (NULL == oph->url)
    416     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    417   eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
    418   if (NULL == eh)
    419     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    420   if (0 != tms)
    421   {
    422     GNUNET_break (CURLE_OK ==
    423                   curl_easy_setopt (eh,
    424                                     CURLOPT_TIMEOUT_MS,
    425                                     (long) (tms + 100L)));
    426   }
    427   oph->job = GNUNET_CURL_job_add (oph->ctx,
    428                                   eh,
    429                                   &handle_get_order_finished,
    430                                   oph);
    431   if (NULL == oph->job)
    432     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    433   return TALER_EC_NONE;
    434 }
    435 
    436 
    437 void
    438 TALER_MERCHANT_get_orders_cancel (
    439   struct TALER_MERCHANT_GetOrdersHandle *oph)
    440 {
    441   if (NULL != oph->job)
    442   {
    443     GNUNET_CURL_job_cancel (oph->job);
    444     oph->job = NULL;
    445   }
    446   GNUNET_free (oph->url);
    447   GNUNET_free (oph->order_id);
    448   GNUNET_free (oph->session_id);
    449   GNUNET_free (oph->base_url);
    450   GNUNET_free (oph);
    451 }
    452 
    453 
    454 /* end of merchant_api_get-orders-ORDER_ID-new.c */