merchant

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

merchant_api_get-private-orders-ORDER_ID.c (20318B)


      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-private-orders-ORDER_ID-new.c
     19  * @brief Implementation of the GET /private/orders/$ORDER_ID request
     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-private-orders-ORDER_ID.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include <taler/taler_json_lib.h>
     31 
     32 
     33 /**
     34  * Maximum number of refund details we return.
     35  */
     36 #define MAX_REFUND_DETAILS 1024
     37 
     38 /**
     39  * Maximum number of wire details we return.
     40  */
     41 #define MAX_WIRE_DETAILS 1024
     42 
     43 
     44 /**
     45  * Handle for a GET /private/orders/$ORDER_ID operation.
     46  */
     47 struct TALER_MERCHANT_GetPrivateOrderHandle
     48 {
     49   /**
     50    * Base URL of the merchant backend.
     51    */
     52   char *base_url;
     53 
     54   /**
     55    * The full URL for this request.
     56    */
     57   char *url;
     58 
     59   /**
     60    * Handle for the request.
     61    */
     62   struct GNUNET_CURL_Job *job;
     63 
     64   /**
     65    * Function to call with the result.
     66    */
     67   TALER_MERCHANT_GetPrivateOrderCallback cb;
     68 
     69   /**
     70    * Closure for @a cb.
     71    */
     72   TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE *cb_cls;
     73 
     74   /**
     75    * Reference to the execution context.
     76    */
     77   struct GNUNET_CURL_Context *ctx;
     78 
     79   /**
     80    * Order ID.
     81    */
     82   char *order_id;
     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    * Long-poll ETag to suppress unchanged responses.
     96    */
     97   struct GNUNET_ShortHashCode lp_not_etag;
     98 
     99   /**
    100    * True if @e lp_not_etag was set.
    101    */
    102   bool have_lp_not_etag;
    103 
    104   /**
    105    * If true, try to obtain wire transfer status from the exchange.
    106    */
    107   bool transfer;
    108 
    109   /**
    110    * If true, allow refunded orders under already_paid_order_id.
    111    */
    112   bool allow_refunded_for_repurchase;
    113 };
    114 
    115 
    116 /**
    117  * Handle HTTP OK response for an unpaid order.
    118  *
    119  * @param oph handle for the request
    120  * @param[in,out] osr HTTP response we got
    121  */
    122 static void
    123 handle_unpaid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
    124                struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
    125 {
    126   struct GNUNET_JSON_Specification spec[] = {
    127     GNUNET_JSON_spec_mark_optional (
    128       GNUNET_JSON_spec_string (
    129         "already_paid_order_id",
    130         &osr->details.ok.details.unpaid.already_paid_order_id),
    131       NULL),
    132     GNUNET_JSON_spec_mark_optional (
    133       GNUNET_JSON_spec_string (
    134         "already_paid_fulfillment_url",
    135         &osr->details.ok.details.unpaid.already_paid_fulfillment_url),
    136       NULL),
    137     GNUNET_JSON_spec_string (
    138       "taler_pay_uri",
    139       &osr->details.ok.details.unpaid.taler_pay_uri),
    140     GNUNET_JSON_spec_string (
    141       "summary",
    142       &osr->details.ok.details.unpaid.summary),
    143     GNUNET_JSON_spec_mark_optional (
    144       GNUNET_JSON_spec_object_const (
    145         "proto_contract_terms",
    146         &osr->details.ok.details.unpaid.proto_contract_terms),
    147       NULL),
    148     GNUNET_JSON_spec_string (
    149       "order_status_url",
    150       &osr->details.ok.details.unpaid.order_status_url),
    151     GNUNET_JSON_spec_timestamp (
    152       "creation_time",
    153       &osr->details.ok.details.unpaid.creation_time),
    154     GNUNET_JSON_spec_mark_optional (
    155       GNUNET_JSON_spec_timestamp (
    156         "pay_deadline",
    157         &osr->details.ok.details.unpaid.pay_deadline),
    158       NULL),
    159     GNUNET_JSON_spec_end ()
    160   };
    161 
    162   if (GNUNET_OK !=
    163       GNUNET_JSON_parse (osr->hr.reply,
    164                          spec,
    165                          NULL, NULL))
    166   {
    167     GNUNET_break_op (0);
    168     osr->hr.http_status = 0;
    169     osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    170     oph->cb (oph->cb_cls,
    171              osr);
    172     return;
    173   }
    174   osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID;
    175   oph->cb (oph->cb_cls,
    176            osr);
    177 }
    178 
    179 
    180 /**
    181  * Handle HTTP OK response for a claimed order.
    182  *
    183  * @param oph handle for the request
    184  * @param[in,out] osr HTTP response we got
    185  */
    186 static void
    187 handle_claimed (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
    188                 struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
    189 {
    190   struct GNUNET_JSON_Specification spec[] = {
    191     GNUNET_JSON_spec_object_const (
    192       "contract_terms",
    193       &osr->details.ok.details.claimed.contract_terms),
    194     GNUNET_JSON_spec_mark_optional (
    195       GNUNET_JSON_spec_string (
    196         "order_status_url",
    197         &osr->details.ok.details.claimed.order_status_url),
    198       NULL),
    199     GNUNET_JSON_spec_end ()
    200   };
    201 
    202   if (GNUNET_OK !=
    203       GNUNET_JSON_parse (osr->hr.reply,
    204                          spec,
    205                          NULL, NULL))
    206   {
    207     GNUNET_break_op (0);
    208     osr->hr.http_status = 0;
    209     osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    210     oph->cb (oph->cb_cls,
    211              osr);
    212     return;
    213   }
    214   osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED;
    215   oph->cb (oph->cb_cls,
    216            osr);
    217 }
    218 
    219 
    220 /**
    221  * Handle HTTP OK response for a paid order.
    222  *
    223  * @param oph handle for the request
    224  * @param[in,out] osr HTTP response we got
    225  */
    226 static void
    227 handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
    228              struct TALER_MERCHANT_GetPrivateOrderResponse *osr)
    229 {
    230   uint32_t hc32;
    231   uint32_t ci32;
    232   bool ci_missing;
    233   const json_t *wire_details;
    234   const json_t *refund_details;
    235   struct GNUNET_JSON_Specification spec[] = {
    236     GNUNET_JSON_spec_bool ("refunded",
    237                            &osr->details.ok.details.paid.refunded),
    238     GNUNET_JSON_spec_bool ("refund_pending",
    239                            &osr->details.ok.details.paid.refund_pending),
    240     GNUNET_JSON_spec_bool ("wired",
    241                            &osr->details.ok.details.paid.wired),
    242     TALER_JSON_spec_amount_any ("deposit_total",
    243                                 &osr->details.ok.details.paid.deposit_total),
    244     TALER_JSON_spec_ec ("exchange_code",
    245                         &osr->details.ok.details.paid.exchange_ec),
    246     GNUNET_JSON_spec_uint32 ("exchange_http_status",
    247                              &hc32),
    248     TALER_JSON_spec_amount_any ("refund_amount",
    249                                 &osr->details.ok.details.paid.refund_amount),
    250     GNUNET_JSON_spec_object_const (
    251       "contract_terms",
    252       &osr->details.ok.details.paid.contract_terms),
    253     GNUNET_JSON_spec_mark_optional (
    254       GNUNET_JSON_spec_uint32 ("choice_index",
    255                                &ci32),
    256       &ci_missing),
    257     GNUNET_JSON_spec_array_const ("wire_details",
    258                                   &wire_details),
    259     GNUNET_JSON_spec_array_const ("refund_details",
    260                                   &refund_details),
    261     GNUNET_JSON_spec_mark_optional (
    262       GNUNET_JSON_spec_timestamp ("last_payment",
    263                                   &osr->details.ok.details.paid.last_payment),
    264       NULL),
    265     GNUNET_JSON_spec_string (
    266       "order_status_url",
    267       &osr->details.ok.details.paid.order_status_url),
    268     GNUNET_JSON_spec_end ()
    269   };
    270 
    271   if (GNUNET_OK !=
    272       GNUNET_JSON_parse (osr->hr.reply,
    273                          spec,
    274                          NULL, NULL))
    275   {
    276     GNUNET_break_op (0);
    277     osr->hr.http_status = 0;
    278     osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    279     oph->cb (oph->cb_cls,
    280              osr);
    281     return;
    282   }
    283   osr->details.ok.status = TALER_MERCHANT_OSC_PAID;
    284   osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32;
    285   osr->details.ok.details.paid.choice_index = ci_missing
    286                                                ? -1
    287                                                : (int) ci32;
    288   {
    289     unsigned int wts_len = (unsigned int) json_array_size (wire_details);
    290     unsigned int ref_len = (unsigned int) json_array_size (refund_details);
    291 
    292     if ( (json_array_size (wire_details) != (size_t) wts_len) ||
    293          (wts_len > MAX_WIRE_DETAILS) )
    294     {
    295       GNUNET_break (0);
    296       osr->hr.http_status = 0;
    297       osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    298       oph->cb (oph->cb_cls,
    299                osr);
    300       return;
    301     }
    302     if ( (json_array_size (refund_details) != (size_t) ref_len) ||
    303          (ref_len > MAX_REFUND_DETAILS) )
    304     {
    305       GNUNET_break (0);
    306       osr->hr.http_status = 0;
    307       osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    308       oph->cb (oph->cb_cls,
    309                osr);
    310       return;
    311     }
    312     {
    313       struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)];
    314       struct TALER_MERCHANT_GetPrivateOrderRefundDetail ref[
    315         GNUNET_NZL (ref_len)];
    316 
    317       for (unsigned int i = 0; i<wts_len; i++)
    318       {
    319         struct TALER_MERCHANT_WireTransfer *wt = &wts[i];
    320         const json_t *w = json_array_get (wire_details,
    321                                           i);
    322         struct GNUNET_JSON_Specification ispec[] = {
    323           TALER_JSON_spec_web_url ("exchange_url",
    324                                    &wt->exchange_url),
    325           GNUNET_JSON_spec_fixed_auto ("wtid",
    326                                        &wt->wtid),
    327           GNUNET_JSON_spec_timestamp ("execution_time",
    328                                       &wt->execution_time),
    329           TALER_JSON_spec_amount_any ("amount",
    330                                       &wt->total_amount),
    331           GNUNET_JSON_spec_mark_optional (
    332             TALER_JSON_spec_amount_any ("deposit_fee",
    333                                         &wt->deposit_fee),
    334             NULL),
    335           GNUNET_JSON_spec_bool ("confirmed",
    336                                  &wt->confirmed),
    337           GNUNET_JSON_spec_mark_optional (
    338             GNUNET_JSON_spec_uint64 ("expected_transfer_serial_id",
    339                                      &wt->expected_transfer_serial_id),
    340             NULL),
    341           GNUNET_JSON_spec_end ()
    342         };
    343 
    344         if (GNUNET_OK !=
    345             GNUNET_JSON_parse (w,
    346                                ispec,
    347                                NULL, NULL))
    348         {
    349           GNUNET_break_op (0);
    350           osr->hr.http_status = 0;
    351           osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    352           oph->cb (oph->cb_cls,
    353                    osr);
    354           return;
    355         }
    356       }
    357 
    358       for (unsigned int i = 0; i<ref_len; i++)
    359       {
    360         struct TALER_MERCHANT_GetPrivateOrderRefundDetail *ro = &ref[i];
    361         const json_t *w = json_array_get (refund_details,
    362                                           i);
    363         struct GNUNET_JSON_Specification ispec[] = {
    364           TALER_JSON_spec_amount_any ("amount",
    365                                       &ro->refund_amount),
    366           GNUNET_JSON_spec_string ("reason",
    367                                    &ro->reason),
    368           GNUNET_JSON_spec_bool ("pending",
    369                                  &ro->pending),
    370           GNUNET_JSON_spec_timestamp ("timestamp",
    371                                       &ro->refund_time),
    372           GNUNET_JSON_spec_end ()
    373         };
    374 
    375         if (GNUNET_OK !=
    376             GNUNET_JSON_parse (w,
    377                                ispec,
    378                                NULL, NULL))
    379         {
    380           GNUNET_break_op (0);
    381           osr->hr.http_status = 0;
    382           osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    383           oph->cb (oph->cb_cls,
    384                    osr);
    385           return;
    386         }
    387       }
    388 
    389       osr->details.ok.details.paid.wts = wts;
    390       osr->details.ok.details.paid.wts_len = wts_len;
    391       osr->details.ok.details.paid.refunds = ref;
    392       osr->details.ok.details.paid.refunds_len = ref_len;
    393       oph->cb (oph->cb_cls,
    394                osr);
    395     }
    396   }
    397 }
    398 
    399 
    400 /**
    401  * Function called when we're done processing the
    402  * HTTP GET /private/orders/$ORDER_ID request.
    403  *
    404  * @param cls the `struct TALER_MERCHANT_GetPrivateOrderHandle`
    405  * @param response_code HTTP response code, 0 on error
    406  * @param response response body, NULL if not in JSON
    407  */
    408 static void
    409 handle_get_private_order_finished (void *cls,
    410                                    long response_code,
    411                                    const void *response)
    412 {
    413   struct TALER_MERCHANT_GetPrivateOrderHandle *oph = cls;
    414   const json_t *json = response;
    415   const char *order_status;
    416   struct TALER_MERCHANT_GetPrivateOrderResponse osr = {
    417     .hr.http_status = (unsigned int) response_code,
    418     .hr.reply = json
    419   };
    420 
    421   oph->job = NULL;
    422   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    423               "Got /private/orders/$ORDER_ID response with status code %u\n",
    424               (unsigned int) response_code);
    425   switch (response_code)
    426   {
    427   case MHD_HTTP_OK:
    428     /* see below */
    429     break;
    430   case MHD_HTTP_NOT_MODIFIED:
    431     oph->cb (oph->cb_cls,
    432              &osr);
    433     TALER_MERCHANT_get_private_order_cancel (oph);
    434     return;
    435   case MHD_HTTP_ACCEPTED:
    436     oph->cb (oph->cb_cls,
    437              &osr);
    438     TALER_MERCHANT_get_private_order_cancel (oph);
    439     return;
    440   case MHD_HTTP_UNAUTHORIZED:
    441     osr.hr.ec = TALER_JSON_get_error_code (json);
    442     osr.hr.hint = TALER_JSON_get_error_hint (json);
    443     oph->cb (oph->cb_cls,
    444              &osr);
    445     TALER_MERCHANT_get_private_order_cancel (oph);
    446     return;
    447   case MHD_HTTP_NOT_FOUND:
    448     osr.hr.ec = TALER_JSON_get_error_code (json);
    449     osr.hr.hint = TALER_JSON_get_error_hint (json);
    450     oph->cb (oph->cb_cls,
    451              &osr);
    452     TALER_MERCHANT_get_private_order_cancel (oph);
    453     return;
    454   case MHD_HTTP_GATEWAY_TIMEOUT:
    455     osr.hr.ec = TALER_JSON_get_error_code (json);
    456     osr.hr.hint = TALER_JSON_get_error_hint (json);
    457     oph->cb (oph->cb_cls,
    458              &osr);
    459     TALER_MERCHANT_get_private_order_cancel (oph);
    460     return;
    461   default:
    462     osr.hr.ec = TALER_JSON_get_error_code (json);
    463     osr.hr.hint = TALER_JSON_get_error_hint (json);
    464     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    465                 "Polling order status failed with HTTP status code %u/%d\n",
    466                 (unsigned int) response_code,
    467                 (int) osr.hr.ec);
    468     GNUNET_break_op (0);
    469     oph->cb (oph->cb_cls,
    470              &osr);
    471     TALER_MERCHANT_get_private_order_cancel (oph);
    472     return;
    473   }
    474 
    475   order_status = json_string_value (json_object_get (json,
    476                                                      "order_status"));
    477   if (NULL == order_status)
    478   {
    479     GNUNET_break_op (0);
    480     osr.hr.http_status = 0;
    481     osr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    482     oph->cb (oph->cb_cls,
    483              &osr);
    484     TALER_MERCHANT_get_private_order_cancel (oph);
    485     return;
    486   }
    487 
    488   if (0 == strcmp ("paid",
    489                    order_status))
    490   {
    491     handle_paid (oph,
    492                  &osr);
    493   }
    494   else if (0 == strcmp ("claimed",
    495                         order_status))
    496   {
    497     handle_claimed (oph,
    498                     &osr);
    499   }
    500   else if (0 == strcmp ("unpaid",
    501                         order_status))
    502   {
    503     handle_unpaid (oph,
    504                    &osr);
    505   }
    506   else
    507   {
    508     GNUNET_break_op (0);
    509     osr.hr.http_status = 0;
    510     osr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    511     oph->cb (oph->cb_cls,
    512              &osr);
    513   }
    514   TALER_MERCHANT_get_private_order_cancel (oph);
    515 }
    516 
    517 
    518 struct TALER_MERCHANT_GetPrivateOrderHandle *
    519 TALER_MERCHANT_get_private_order_create (
    520   struct GNUNET_CURL_Context *ctx,
    521   const char *url,
    522   const char *order_id)
    523 {
    524   struct TALER_MERCHANT_GetPrivateOrderHandle *oph;
    525 
    526   oph = GNUNET_new (struct TALER_MERCHANT_GetPrivateOrderHandle);
    527   oph->ctx = ctx;
    528   oph->base_url = GNUNET_strdup (url);
    529   oph->order_id = GNUNET_strdup (order_id);
    530   return oph;
    531 }
    532 
    533 
    534 enum GNUNET_GenericReturnValue
    535 TALER_MERCHANT_get_private_order_set_options_ (
    536   struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
    537   unsigned int num_options,
    538   const struct TALER_MERCHANT_GetPrivateOrderOptionValue *options)
    539 {
    540   for (unsigned int i = 0; i < num_options; i++)
    541   {
    542     const struct TALER_MERCHANT_GetPrivateOrderOptionValue *opt =
    543       &options[i];
    544 
    545     switch (opt->option)
    546     {
    547     case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_END:
    548       return GNUNET_OK;
    549     case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_SESSION_ID:
    550       GNUNET_free (oph->session_id);
    551       if (NULL != opt->details.session_id)
    552         oph->session_id = GNUNET_strdup (opt->details.session_id);
    553       break;
    554     case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TIMEOUT:
    555       oph->timeout = opt->details.timeout;
    556       break;
    557     case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TRANSFER:
    558       oph->transfer = opt->details.transfer;
    559       break;
    560     case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_LP_NOT_ETAG:
    561       if (NULL != opt->details.lp_not_etag)
    562       {
    563         oph->lp_not_etag = *opt->details.lp_not_etag;
    564         oph->have_lp_not_etag = true;
    565       }
    566       break;
    567     case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE:
    568       oph->allow_refunded_for_repurchase
    569         = opt->details.allow_refunded_for_repurchase;
    570       break;
    571     default:
    572       GNUNET_break (0);
    573       return GNUNET_NO;
    574     }
    575   }
    576   return GNUNET_OK;
    577 }
    578 
    579 
    580 enum TALER_ErrorCode
    581 TALER_MERCHANT_get_private_order_start (
    582   struct TALER_MERCHANT_GetPrivateOrderHandle *oph,
    583   TALER_MERCHANT_GetPrivateOrderCallback cb,
    584   TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE *cb_cls)
    585 {
    586   CURL *eh;
    587   unsigned int tms;
    588 
    589   oph->cb = cb;
    590   oph->cb_cls = cb_cls;
    591   tms = (unsigned int) (oph->timeout.rel_value_us
    592                         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    593   {
    594     char *path;
    595     char timeout_ms[32];
    596     char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1];
    597 
    598     GNUNET_snprintf (timeout_ms,
    599                      sizeof (timeout_ms),
    600                      "%u",
    601                      tms);
    602     if (oph->have_lp_not_etag)
    603     {
    604       char *end;
    605 
    606       end = GNUNET_STRINGS_data_to_string (
    607         &oph->lp_not_etag,
    608         sizeof (oph->lp_not_etag),
    609         etag_str,
    610         sizeof (etag_str) - 1);
    611       *end = '\0';
    612     }
    613     GNUNET_asprintf (&path,
    614                      "private/orders/%s",
    615                      oph->order_id);
    616     oph->url = TALER_url_join (oph->base_url,
    617                                path,
    618                                "session_id",
    619                                oph->session_id,
    620                                "timeout_ms",
    621                                (0 != tms)
    622                                ? timeout_ms
    623                                : NULL,
    624                                "transfer",
    625                                oph->transfer
    626                                ? "YES"
    627                                : NULL,
    628                                "lp_not_etag",
    629                                oph->have_lp_not_etag
    630                                ? etag_str
    631                                : NULL,
    632                                "allow_refunded_for_repurchase",
    633                                oph->allow_refunded_for_repurchase
    634                                ? "YES"
    635                                : NULL,
    636                                NULL);
    637     GNUNET_free (path);
    638   }
    639   if (NULL == oph->url)
    640     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    641   eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
    642   if (NULL == eh)
    643     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    644   if (0 != tms)
    645   {
    646     GNUNET_break (CURLE_OK ==
    647                   curl_easy_setopt (eh,
    648                                     CURLOPT_TIMEOUT_MS,
    649                                     (long) (tms + 100L)));
    650   }
    651   oph->job = GNUNET_CURL_job_add (oph->ctx,
    652                                   eh,
    653                                   &handle_get_private_order_finished,
    654                                   oph);
    655   if (NULL == oph->job)
    656     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    657   return TALER_EC_NONE;
    658 }
    659 
    660 
    661 void
    662 TALER_MERCHANT_get_private_order_cancel (
    663   struct TALER_MERCHANT_GetPrivateOrderHandle *oph)
    664 {
    665   if (NULL != oph->job)
    666   {
    667     GNUNET_CURL_job_cancel (oph->job);
    668     oph->job = NULL;
    669   }
    670   GNUNET_free (oph->url);
    671   GNUNET_free (oph->order_id);
    672   GNUNET_free (oph->session_id);
    673   GNUNET_free (oph->base_url);
    674   GNUNET_free (oph);
    675 }
    676 
    677 
    678 /* end of merchant_api_get-private-orders-ORDER_ID-new.c */