merchant

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

merchant_api_post-private-orders-ORDER_ID-collect.c (7774B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 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 src/lib/merchant_api_post-private-orders-ORDER_ID-collect.c
     19  * @brief Implementation of the POST /private/orders/$ORDER_ID/collect request
     20  * @author Bohdan Potuzhnyi
     21  * @author Volodymyr Potuzhnyi
     22  */
     23 #include "platform.h"
     24 #include <curl/curl.h>
     25 #include <jansson.h>
     26 #include <microhttpd.h> /* just for HTTP status codes */
     27 #include <gnunet/gnunet_util_lib.h>
     28 #include <gnunet/gnunet_curl_lib.h>
     29 #include <taler/merchant/post-private-orders-ORDER_ID-collect.h>
     30 #include "merchant_api_curl_defaults.h"
     31 #include "merchant_api_common.h"
     32 #include <taler/taler_json_lib.h>
     33 #include <taler/taler_curl_lib.h>
     34 
     35 
     36 /**
     37  * Handle for a POST /private/orders/$ORDER_ID/collect operation.
     38  */
     39 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle
     40 {
     41   /**
     42    * Base URL of the merchant backend.
     43    */
     44   char *base_url;
     45 
     46   /**
     47    * The full URL for this request.
     48    */
     49   char *url;
     50 
     51   /**
     52    * Handle for the request.
     53    */
     54   struct GNUNET_CURL_Job *job;
     55 
     56   /**
     57    * Function to call with the result.
     58    */
     59   TALER_MERCHANT_PostPrivateOrdersCollectCallback cb;
     60 
     61   /**
     62    * Closure for @a cb.
     63    */
     64   TALER_MERCHANT_POST_PRIVATE_ORDERS_COLLECT_RESULT_CLOSURE *cb_cls;
     65 
     66   /**
     67    * Reference to the execution context.
     68    */
     69   struct GNUNET_CURL_Context *ctx;
     70 
     71   /**
     72    * Minor context that holds body and headers.
     73    */
     74   struct TALER_CURL_PostContext post_ctx;
     75 
     76   /**
     77    * Identifier of the order to collect.
     78    */
     79   char *order_id;
     80 
     81   /**
     82    * Session ID to store with the payment, NULL for none.
     83    */
     84   char *session_id;
     85 
     86   /**
     87    * Choice to complete, negative if the contract has no choices.
     88    */
     89   int16_t choice_index;
     90 };
     91 
     92 
     93 /**
     94  * Function called when we're done processing the
     95  * HTTP POST /private/orders/$ORDER_ID/collect request.
     96  *
     97  * @param cls the `struct TALER_MERCHANT_PostPrivateOrdersCollectHandle`
     98  * @param response_code HTTP response code, 0 on error
     99  * @param response response body, NULL if not in JSON
    100  */
    101 static void
    102 handle_collect_finished (void *cls,
    103                          long response_code,
    104                          const void *response)
    105 {
    106   struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch = cls;
    107   const json_t *json = response;
    108   struct TALER_MERCHANT_PostPrivateOrdersCollectResponse cr = {
    109     .hr.http_status = (unsigned int) response_code,
    110     .hr.reply = json
    111   };
    112 
    113   poch->job = NULL;
    114   switch (response_code)
    115   {
    116   case 0:
    117     cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    118     break;
    119   case MHD_HTTP_OK:
    120     {
    121       struct GNUNET_JSON_Specification spec[] = {
    122         GNUNET_JSON_spec_fixed_auto ("sig",
    123                                      &cr.details.ok.merchant_sig),
    124         GNUNET_JSON_spec_mark_optional (
    125           GNUNET_JSON_spec_string ("pos_confirmation",
    126                                    &cr.details.ok.pos_confirmation),
    127           NULL),
    128         GNUNET_JSON_spec_end ()
    129       };
    130 
    131       /* The response is the one of paying the order; "token_sigs" is
    132          never present, as orders with token outputs cannot be
    133          collected. */
    134       if (GNUNET_OK !=
    135           GNUNET_JSON_parse (json,
    136                              spec,
    137                              NULL, NULL))
    138       {
    139         GNUNET_break_op (0);
    140         cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    141         cr.hr.http_status = 0;
    142         cr.hr.hint = "sig field missing in response";
    143       }
    144     }
    145     break;
    146   case MHD_HTTP_UNAUTHORIZED:
    147     cr.hr.ec = TALER_JSON_get_error_code (json);
    148     cr.hr.hint = TALER_JSON_get_error_hint (json);
    149     break;
    150   case MHD_HTTP_NOT_FOUND:
    151     cr.hr.ec = TALER_JSON_get_error_code (json);
    152     cr.hr.hint = TALER_JSON_get_error_hint (json);
    153     break;
    154   case MHD_HTTP_CONFLICT:
    155     cr.hr.ec = TALER_JSON_get_error_code (json);
    156     cr.hr.hint = TALER_JSON_get_error_hint (json);
    157     break;
    158   default:
    159     GNUNET_break_op (0);
    160     TALER_MERCHANT_parse_error_details_ (json,
    161                                          response_code,
    162                                          &cr.hr);
    163     break;
    164   }
    165   poch->cb (poch->cb_cls,
    166             &cr);
    167   TALER_MERCHANT_post_private_orders_collect_cancel (poch);
    168 }
    169 
    170 
    171 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *
    172 TALER_MERCHANT_post_private_orders_collect_create (
    173   struct GNUNET_CURL_Context *ctx,
    174   const char *url,
    175   const char *order_id,
    176   const char *session_id,
    177   int16_t choice_index)
    178 {
    179   struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch;
    180 
    181   poch = GNUNET_new (struct TALER_MERCHANT_PostPrivateOrdersCollectHandle);
    182   poch->ctx = ctx;
    183   poch->base_url = GNUNET_strdup (url);
    184   poch->order_id = GNUNET_strdup (order_id);
    185   poch->choice_index = choice_index;
    186   if (NULL != session_id)
    187     poch->session_id = GNUNET_strdup (session_id);
    188   return poch;
    189 }
    190 
    191 
    192 enum TALER_ErrorCode
    193 TALER_MERCHANT_post_private_orders_collect_start (
    194   struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch,
    195   TALER_MERCHANT_PostPrivateOrdersCollectCallback cb,
    196   TALER_MERCHANT_POST_PRIVATE_ORDERS_COLLECT_RESULT_CLOSURE *cb_cls)
    197 {
    198   json_t *req_obj;
    199   CURL *eh;
    200 
    201   poch->cb = cb;
    202   poch->cb_cls = cb_cls;
    203   {
    204     char *path;
    205 
    206     GNUNET_asprintf (&path,
    207                      "private/orders/%s/collect",
    208                      poch->order_id);
    209     poch->url = TALER_url_join (poch->base_url,
    210                                 path,
    211                                 NULL);
    212     GNUNET_free (path);
    213   }
    214   if (NULL == poch->url)
    215     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    216   req_obj = GNUNET_JSON_PACK (
    217     GNUNET_JSON_pack_allow_null (
    218       GNUNET_JSON_pack_string ("session_id",
    219                                poch->session_id)));
    220   GNUNET_assert (NULL != req_obj);
    221   if (0 <= poch->choice_index)
    222     GNUNET_assert (0 ==
    223                    json_object_set_new (req_obj,
    224                                         "choice_index",
    225                                         json_integer (poch->choice_index)));
    226   eh = TALER_MERCHANT_curl_easy_get_ (poch->url);
    227   if ( (NULL == eh) ||
    228        (GNUNET_OK !=
    229         TALER_curl_easy_post (&poch->post_ctx,
    230                               eh,
    231                               req_obj)) )
    232   {
    233     GNUNET_break (0);
    234     json_decref (req_obj);
    235     if (NULL != eh)
    236       curl_easy_cleanup (eh);
    237     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    238   }
    239   json_decref (req_obj);
    240   poch->job = GNUNET_CURL_job_add2 (poch->ctx,
    241                                     eh,
    242                                     poch->post_ctx.headers,
    243                                     &handle_collect_finished,
    244                                     poch);
    245   if (NULL == poch->job)
    246     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    247   return TALER_EC_NONE;
    248 }
    249 
    250 
    251 void
    252 TALER_MERCHANT_post_private_orders_collect_cancel (
    253   struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch)
    254 {
    255   if (NULL != poch->job)
    256   {
    257     GNUNET_CURL_job_cancel (poch->job);
    258     poch->job = NULL;
    259   }
    260   TALER_curl_easy_post_finished (&poch->post_ctx);
    261   GNUNET_free (poch->order_id);
    262   GNUNET_free (poch->session_id);
    263   GNUNET_free (poch->url);
    264   GNUNET_free (poch->base_url);
    265   GNUNET_free (poch);
    266 }
    267 
    268 
    269 /* end of merchant_api_post-private-orders-ORDER_ID-collect.c */