LCOV - code coverage report
Current view: top level - lib - merchant_api_post_order_abort.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 85 151 56.3 %
Date: 2025-06-23 16:22:09 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2023 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify
       6             :   it under the terms of the GNU Lesser General Public License as
       7             :   published by the Free Software Foundation; either version 2.1,
       8             :   or (at your option) any later version.
       9             : 
      10             :   TALER is distributed in the hope that it will be useful,
      11             :   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :   GNU Lesser General Public License for more details.
      14             : 
      15             :   You should have received a copy of the GNU Lesser General
      16             :   Public License along with TALER; see the file COPYING.LGPL.
      17             :   If not, see <http://www.gnu.org/licenses/>
      18             : */
      19             : /**
      20             :  * @file merchant_api_post_order_abort.c
      21             :  * @brief Implementation of the POST /orders/$ID/abort request
      22             :  *        of the merchant's HTTP API
      23             :  * @author Christian Grothoff
      24             :  * @author Marcello Stanisci
      25             :  */
      26             : #include "platform.h"
      27             : #include <curl/curl.h>
      28             : #include <jansson.h>
      29             : #include <microhttpd.h> /* just for HTTP status codes */
      30             : #include <gnunet/gnunet_util_lib.h>
      31             : #include <gnunet/gnunet_curl_lib.h>
      32             : #include "taler_merchant_service.h"
      33             : #include "merchant_api_curl_defaults.h"
      34             : #include "merchant_api_common.h"
      35             : #include <taler/taler_json_lib.h>
      36             : #include <taler/taler_signatures.h>
      37             : #include <taler/taler_exchange_service.h>
      38             : #include <taler/taler_curl_lib.h>
      39             : 
      40             : 
      41             : /**
      42             :  * Maximum number of refunds we return.
      43             :  */
      44             : #define MAX_REFUNDS 1024
      45             : 
      46             : 
      47             : /**
      48             :  * @brief An abort Handle
      49             :  */
      50             : struct TALER_MERCHANT_OrderAbortHandle
      51             : {
      52             :   /**
      53             :    * Hash of the contract.
      54             :    */
      55             :   struct TALER_PrivateContractHashP h_contract_terms;
      56             : 
      57             :   /**
      58             :    * Public key of the merchant.
      59             :    */
      60             :   struct TALER_MerchantPublicKeyP merchant_pub;
      61             : 
      62             :   /**
      63             :    * The url for this request.
      64             :    */
      65             :   char *url;
      66             : 
      67             :   /**
      68             :    * Handle for the request.
      69             :    */
      70             :   struct GNUNET_CURL_Job *job;
      71             : 
      72             :   /**
      73             :    * Function to call with the result.
      74             :    */
      75             :   TALER_MERCHANT_AbortCallback abort_cb;
      76             : 
      77             :   /**
      78             :    * Closure for @a abort_cb.
      79             :    */
      80             :   void *abort_cb_cls;
      81             : 
      82             :   /**
      83             :    * Reference to the execution context.
      84             :    */
      85             :   struct GNUNET_CURL_Context *ctx;
      86             : 
      87             :   /**
      88             :    * Minor context that holds body and headers.
      89             :    */
      90             :   struct TALER_CURL_PostContext post_ctx;
      91             : 
      92             :   /**
      93             :    * The coins we are aborting on.
      94             :    */
      95             :   struct TALER_MERCHANT_AbortCoin *coins;
      96             : 
      97             :   /**
      98             :    * Number of @e coins we are paying with.
      99             :    */
     100             :   unsigned int num_coins;
     101             : 
     102             : };
     103             : 
     104             : 
     105             : /**
     106             :  * Check that the response for an abort is well-formed,
     107             :  * and call the application callback with the result if it is
     108             :  * OK. Otherwise returns #GNUNET_SYSERR.
     109             :  *
     110             :  * @param oah handle to operation that created the reply
     111             :  * @param[in] ar abort response, partially initialized
     112             :  * @param json the reply to parse
     113             :  * @return #GNUNET_OK on success
     114             :  */
     115             : static enum GNUNET_GenericReturnValue
     116           2 : check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
     117             :                     struct TALER_MERCHANT_AbortResponse *ar,
     118             :                     const json_t *json)
     119             : {
     120             :   const json_t *refunds;
     121             :   unsigned int num_refunds;
     122             :   struct GNUNET_JSON_Specification spec[] = {
     123           2 :     GNUNET_JSON_spec_array_const ("refunds",
     124             :                                   &refunds),
     125           2 :     GNUNET_JSON_spec_end ()
     126             :   };
     127             : 
     128           2 :   if (GNUNET_OK !=
     129           2 :       GNUNET_JSON_parse (json,
     130             :                          spec,
     131             :                          NULL, NULL))
     132             :   {
     133           0 :     GNUNET_break_op (0);
     134           0 :     return GNUNET_SYSERR;
     135             :   }
     136           2 :   num_refunds = (unsigned int) json_array_size (refunds);
     137           2 :   if ( (json_array_size (refunds) != (size_t)  num_refunds) ||
     138             :        (num_refunds > MAX_REFUNDS) )
     139             :   {
     140           0 :     GNUNET_break (0);
     141           0 :     return GNUNET_SYSERR;
     142             :   }
     143             : 
     144           2 :   {
     145           2 :     struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)];
     146             : 
     147           4 :     for (unsigned int i = 0; i<num_refunds; i++)
     148             :     {
     149           2 :       json_t *refund = json_array_get (refunds, i);
     150             :       uint32_t exchange_status;
     151             :       struct GNUNET_JSON_Specification spec_es[] = {
     152           2 :         GNUNET_JSON_spec_uint32 ("exchange_status",
     153             :                                  &exchange_status),
     154           2 :         GNUNET_JSON_spec_end ()
     155             :       };
     156             : 
     157           2 :       if (GNUNET_OK !=
     158           2 :           GNUNET_JSON_parse (refund,
     159             :                              spec_es,
     160             :                              NULL, NULL))
     161             :       {
     162           0 :         GNUNET_break_op (0);
     163           0 :         return GNUNET_SYSERR;
     164             :       }
     165           2 :       if (MHD_HTTP_OK == exchange_status)
     166             :       {
     167             :         struct GNUNET_JSON_Specification spec_detail[] = {
     168           2 :           GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     169             :                                        &res[i].exchange_sig),
     170           2 :           GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     171             :                                        &res[i].exchange_pub),
     172           2 :           GNUNET_JSON_spec_end ()
     173             :         };
     174             : 
     175           2 :         if (GNUNET_OK !=
     176           2 :             GNUNET_JSON_parse (refund,
     177             :                                spec_detail,
     178             :                                NULL, NULL))
     179             :         {
     180           0 :           GNUNET_break_op (0);
     181           0 :           return GNUNET_SYSERR;
     182             :         }
     183             : 
     184           2 :         if (GNUNET_OK !=
     185           2 :             TALER_exchange_online_refund_confirmation_verify (
     186           2 :               &oah->h_contract_terms,
     187           2 :               &oah->coins[i].coin_pub,
     188           2 :               &oah->merchant_pub,
     189             :               0,                                                   /* transaction id */
     190           2 :               &oah->coins[i].amount_with_fee,
     191           2 :               &res[i].exchange_pub,
     192           2 :               &res[i].exchange_sig))
     193             :         {
     194           0 :           GNUNET_break_op (0);
     195           0 :           return GNUNET_SYSERR;
     196             :         }
     197             :       }
     198             :     }
     199           2 :     ar->details.ok.merchant_pub = &oah->merchant_pub;
     200           2 :     ar->details.ok.num_aborts = num_refunds;
     201           2 :     ar->details.ok.aborts = res;
     202           2 :     oah->abort_cb (oah->abort_cb_cls,
     203             :                    ar);
     204           2 :     oah->abort_cb = NULL;
     205             :   }
     206           2 :   return GNUNET_OK;
     207             : }
     208             : 
     209             : 
     210             : /**
     211             :  * Function called when we're done processing the
     212             :  * abort request.
     213             :  *
     214             :  * @param cls the `struct TALER_MERCHANT_OrderAbortHandle`
     215             :  * @param response_code HTTP response code, 0 on error
     216             :  * @param response response body, NULL if not in JSON
     217             :  */
     218             : static void
     219           2 : handle_abort_finished (void *cls,
     220             :                        long response_code,
     221             :                        const void *response)
     222             : {
     223           2 :   struct TALER_MERCHANT_OrderAbortHandle *oah = cls;
     224           2 :   const json_t *json = response;
     225           2 :   struct TALER_MERCHANT_AbortResponse ar = {
     226           2 :     .hr.http_status = (unsigned int) response_code,
     227             :     .hr.reply = json
     228             :   };
     229             : 
     230           2 :   oah->job = NULL;
     231           2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     232             :               "/pay completed with response code %u\n",
     233             :               (unsigned int) response_code);
     234           2 :   switch (response_code)
     235             :   {
     236           0 :   case 0:
     237           0 :     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     238           0 :     break;
     239           2 :   case MHD_HTTP_OK:
     240           2 :     if (GNUNET_OK ==
     241           2 :         check_abort_refund (oah,
     242             :                             &ar,
     243             :                             json))
     244             :     {
     245           2 :       TALER_MERCHANT_order_abort_cancel (oah);
     246           2 :       return;
     247             :     }
     248           0 :     ar.hr.http_status = 0;
     249           0 :     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     250           0 :     break;
     251           0 :   case MHD_HTTP_BAD_REQUEST:
     252           0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     253           0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     254             :     /* This should never happen, either us or the
     255             :        merchant is buggy (or API version conflict); just
     256             :        pass JSON reply to the application */
     257           0 :     break;
     258           0 :   case MHD_HTTP_FORBIDDEN:
     259           0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     260           0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     261           0 :     break;
     262           0 :   case MHD_HTTP_NOT_FOUND:
     263           0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     264           0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     265             :     /* Nothing really to verify, this should never
     266             :  happen, we should pass the JSON reply to the
     267             :        application */
     268           0 :     break;
     269           0 :   case MHD_HTTP_REQUEST_TIMEOUT:
     270           0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     271           0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     272             :     /* Nothing really to verify, merchant says one of
     273             :        the signatures is invalid; as we checked them,
     274             :        this should never happen, we should pass the JSON
     275             :        reply to the application */
     276           0 :     break;
     277           0 :   case MHD_HTTP_PRECONDITION_FAILED:
     278             :     /* Our *payment* already succeeded fully. */
     279           0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     280           0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     281           0 :     break;
     282           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     283           0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     284           0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     285             :     /* Server had an internal issue; we should retry,
     286             :        but this API leaves this to the application */
     287           0 :     break;
     288           0 :   case MHD_HTTP_BAD_GATEWAY:
     289           0 :     TALER_MERCHANT_parse_error_details_ (json,
     290             :                                          response_code,
     291             :                                          &ar.hr);
     292             :     /* Nothing really to verify, the merchant is blaming the exchange.
     293             :        We should pass the JSON reply to the application */
     294           0 :     break;
     295           0 :   default:
     296             :     /* unexpected response code */
     297           0 :     TALER_MERCHANT_parse_error_details_ (json,
     298             :                                          response_code,
     299             :                                          &ar.hr);
     300           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     301             :                 "Unexpected response code %u/%d\n",
     302             :                 (unsigned int) response_code,
     303             :                 (int) ar.hr.ec);
     304           0 :     GNUNET_break_op (0);
     305           0 :     break;
     306             :   }
     307           0 :   oah->abort_cb (oah->abort_cb_cls,
     308             :                  &ar);
     309           0 :   TALER_MERCHANT_order_abort_cancel (oah);
     310             : }
     311             : 
     312             : 
     313             : struct TALER_MERCHANT_OrderAbortHandle *
     314           2 : TALER_MERCHANT_order_abort (
     315             :   struct GNUNET_CURL_Context *ctx,
     316             :   const char *merchant_url,
     317             :   const char *order_id,
     318             :   const struct TALER_MerchantPublicKeyP *merchant_pub,
     319             :   const struct TALER_PrivateContractHashP *h_contract,
     320             :   unsigned int num_coins,
     321             :   const struct TALER_MERCHANT_AbortCoin coins[static num_coins],
     322             :   TALER_MERCHANT_AbortCallback cb,
     323             :   void *cb_cls)
     324           2 : {
     325             :   struct TALER_MERCHANT_OrderAbortHandle *oah;
     326             :   json_t *abort_obj;
     327             :   json_t *j_coins;
     328             : 
     329           2 :   j_coins = json_array ();
     330           2 :   if (NULL == j_coins)
     331             :   {
     332           0 :     GNUNET_break (0);
     333           0 :     return NULL;
     334             :   }
     335           4 :   for (unsigned int i = 0; i<num_coins; i++)
     336             :   {
     337           2 :     const struct TALER_MERCHANT_AbortCoin *ac = &coins[i];
     338             :     json_t *j_coin;
     339             : 
     340             :     /* create JSON for this coin */
     341           2 :     j_coin = GNUNET_JSON_PACK (
     342             :       GNUNET_JSON_pack_data_auto ("coin_pub",
     343             :                                   &ac->coin_pub),
     344             :       /* FIXME: no longer needed since **v18**, remove eventually! */
     345             :       TALER_JSON_pack_amount ("contribution",
     346             :                               &ac->amount_with_fee),
     347             :       GNUNET_JSON_pack_string ("exchange_url",
     348             :                                ac->exchange_url));
     349           2 :     if (0 !=
     350           2 :         json_array_append_new (j_coins,
     351             :                                j_coin))
     352             :     {
     353           0 :       GNUNET_break (0);
     354           0 :       json_decref (j_coins);
     355           0 :       return NULL;
     356             :     }
     357             :   }
     358           2 :   abort_obj = GNUNET_JSON_PACK (
     359             :     GNUNET_JSON_pack_array_steal ("coins",
     360             :                                   j_coins),
     361             :     GNUNET_JSON_pack_data_auto ("h_contract",
     362             :                                 h_contract));
     363           2 :   oah = GNUNET_new (struct TALER_MERCHANT_OrderAbortHandle);
     364           2 :   oah->h_contract_terms = *h_contract;
     365           2 :   oah->merchant_pub = *merchant_pub;
     366           2 :   oah->ctx = ctx;
     367           2 :   oah->abort_cb = cb;
     368           2 :   oah->abort_cb_cls = cb_cls;
     369             :   {
     370             :     char *path;
     371             : 
     372           2 :     GNUNET_asprintf (&path,
     373             :                      "orders/%s/abort",
     374             :                      order_id);
     375           2 :     oah->url = TALER_url_join (merchant_url,
     376             :                                path,
     377             :                                NULL);
     378           2 :     GNUNET_free (path);
     379             :   }
     380           2 :   if (NULL == oah->url)
     381             :   {
     382           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     383             :                 "Could not construct request URL.\n");
     384           0 :     json_decref (abort_obj);
     385           0 :     GNUNET_free (oah);
     386           0 :     return NULL;
     387             :   }
     388           2 :   oah->num_coins = num_coins;
     389           2 :   oah->coins = GNUNET_new_array (num_coins,
     390             :                                  struct TALER_MERCHANT_AbortCoin);
     391           2 :   GNUNET_memcpy (oah->coins,
     392             :                  coins,
     393             :                  num_coins * sizeof (struct TALER_MERCHANT_AbortCoin));
     394             :   {
     395             :     CURL *eh;
     396             : 
     397           2 :     eh = TALER_MERCHANT_curl_easy_get_ (oah->url);
     398           2 :     if (GNUNET_OK !=
     399           2 :         TALER_curl_easy_post (&oah->post_ctx,
     400             :                               eh,
     401             :                               abort_obj))
     402             :     {
     403           0 :       GNUNET_break (0);
     404           0 :       curl_easy_cleanup (eh);
     405           0 :       json_decref (abort_obj);
     406           0 :       GNUNET_free (oah);
     407           0 :       return NULL;
     408             :     }
     409           2 :     json_decref (abort_obj);
     410           4 :     oah->job = GNUNET_CURL_job_add2 (ctx,
     411             :                                      eh,
     412           2 :                                      oah->post_ctx.headers,
     413             :                                      &handle_abort_finished,
     414             :                                      oah);
     415             :   }
     416           2 :   return oah;
     417             : }
     418             : 
     419             : 
     420             : void
     421           2 : TALER_MERCHANT_order_abort_cancel (
     422             :   struct TALER_MERCHANT_OrderAbortHandle *oah)
     423             : {
     424           2 :   if (NULL != oah->job)
     425             :   {
     426           0 :     GNUNET_CURL_job_cancel (oah->job);
     427           0 :     oah->job = NULL;
     428             :   }
     429           2 :   TALER_curl_easy_post_finished (&oah->post_ctx);
     430           2 :   GNUNET_free (oah->coins);
     431           2 :   GNUNET_free (oah->url);
     432           2 :   GNUNET_free (oah);
     433           2 : }
     434             : 
     435             : 
     436             : /* end of merchant_api_post_order_abort.c */

Generated by: LCOV version 1.16