LCOV - code coverage report
Current view: top level - lib - merchant_api_post-orders-ORDER_ID-abort.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 52.9 % 210 111
Test Date: 2026-09-11 17:45:24 Functions: 100.0 % 5 5

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2014-2026 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 src/lib/merchant_api_post-orders-ORDER_ID-abort.c
      21              :  * @brief Implementation of the POST /orders/$ID/abort request
      22              :  * @author Christian Grothoff
      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/post-orders-ORDER_ID-abort.h>
      31              : #include "merchant_api_curl_defaults.h"
      32              : #include "merchant_api_common.h"
      33              : #include <taler/taler_json_lib.h>
      34              : #include <taler/taler_curl_lib.h>
      35              : #include <taler/taler_signatures.h>
      36              : 
      37              : 
      38              : /**
      39              :  * Maximum number of refunds we return.
      40              :  */
      41              : #define MAX_REFUNDS 1024
      42              : 
      43              : 
      44              : /**
      45              :  * Handle for a POST /orders/$ORDER_ID/abort operation.
      46              :  */
      47              : struct TALER_MERCHANT_PostOrdersAbortHandle
      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_PostOrdersAbortCallback cb;
      68              : 
      69              :   /**
      70              :    * Closure for @a cb.
      71              :    */
      72              :   TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls;
      73              : 
      74              :   /**
      75              :    * Reference to the execution context.
      76              :    */
      77              :   struct GNUNET_CURL_Context *ctx;
      78              : 
      79              :   /**
      80              :    * Minor context that holds body and headers.
      81              :    */
      82              :   struct TALER_CURL_PostContext post_ctx;
      83              : 
      84              :   /**
      85              :    * Order identifier.
      86              :    */
      87              :   char *order_id;
      88              : 
      89              :   /**
      90              :    * Public key of the merchant.
      91              :    */
      92              :   struct TALER_MerchantPublicKeyP merchant_pub;
      93              : 
      94              :   /**
      95              :    * Hash of the contract terms.
      96              :    */
      97              :   struct TALER_PrivateContractHashP h_contract;
      98              : 
      99              :   /**
     100              :    * The coins we are aborting on.
     101              :    */
     102              :   struct TALER_MERCHANT_PostOrdersAbortCoin *coins;
     103              : 
     104              :   /**
     105              :    * Number of @e coins.
     106              :    */
     107              :   unsigned int num_coins;
     108              : };
     109              : 
     110              : 
     111              : /**
     112              :  * Check that the response for an abort is well-formed,
     113              :  * and call the application callback with the result if it is
     114              :  * OK. Otherwise returns #GNUNET_SYSERR.
     115              :  *
     116              :  * @param poah handle to operation that created the reply
     117              :  * @param[in] ar abort response, partially initialized
     118              :  * @param json the reply to parse
     119              :  * @return #GNUNET_OK on success
     120              :  */
     121              : static enum GNUNET_GenericReturnValue
     122            2 : check_abort_refund (struct TALER_MERCHANT_PostOrdersAbortHandle *poah,
     123              :                     struct TALER_MERCHANT_PostOrdersAbortResponse *ar,
     124              :                     const json_t *json)
     125              : {
     126              :   const json_t *refunds;
     127              :   unsigned int num_refunds;
     128              :   struct GNUNET_JSON_Specification spec[] = {
     129            2 :     GNUNET_JSON_spec_array_const ("refunds",
     130              :                                   &refunds),
     131            2 :     GNUNET_JSON_spec_end ()
     132              :   };
     133              : 
     134            2 :   if (GNUNET_OK !=
     135            2 :       GNUNET_JSON_parse (json,
     136              :                          spec,
     137              :                          NULL, NULL))
     138              :   {
     139            0 :     GNUNET_break_op (0);
     140            0 :     return GNUNET_SYSERR;
     141              :   }
     142            2 :   num_refunds = (unsigned int) json_array_size (refunds);
     143            2 :   if ( (json_array_size (refunds) != (size_t) num_refunds) ||
     144            2 :        (num_refunds != poah->num_coins) )
     145              :   {
     146            0 :     GNUNET_break (0);
     147            0 :     return GNUNET_SYSERR;
     148              :   }
     149              : 
     150            2 :   {
     151            2 :     struct TALER_MERCHANT_PostOrdersAbortedCoin res[GNUNET_NZL (num_refunds)];
     152              : 
     153            2 :     memset (res,
     154              :             0,
     155              :             sizeof (res));
     156            4 :     for (unsigned int i = 0; i<num_refunds; i++)
     157              :     {
     158            2 :       json_t *refund = json_array_get (refunds, i);
     159            2 :       struct TALER_MERCHANT_PostOrdersAbortedCoin *ac = &res[i];
     160              :       const char *type;
     161              :       struct GNUNET_JSON_Specification spec_type[] = {
     162            2 :         GNUNET_JSON_spec_string ("type",
     163              :                                  &type),
     164            2 :         GNUNET_JSON_spec_end ()
     165              :       };
     166              : 
     167            2 :       ac->coin_pub = poah->coins[i].coin_pub;
     168            2 :       if (GNUNET_OK !=
     169            2 :           GNUNET_JSON_parse (refund,
     170              :                              spec_type,
     171              :                              NULL, NULL))
     172              :       {
     173            0 :         GNUNET_break_op (0);
     174            0 :         return GNUNET_SYSERR;
     175              :       }
     176            2 :       if (0 == strcmp (type,
     177              :                        "undeposited"))
     178              :       {
     179              :         /* An "undeposited" entry (MerchantAbortPayRefundUndepositedStatus)
     180              :            carries no exchange_status and no refund confirmation to verify. */
     181            0 :         ac->type = TALER_MERCHANT_POAC_UNDEPOSITED;
     182            2 :         continue;
     183              :       }
     184            2 :       if (0 == strcmp (type,
     185              :                        "success"))
     186            2 :       {
     187              :         struct GNUNET_JSON_Specification spec_detail[] = {
     188            2 :           GNUNET_JSON_spec_uint32 ("exchange_status",
     189              :                                    &ac->details.success.exchange_status),
     190            2 :           GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     191              :                                        &ac->details.success.exchange_sig),
     192            2 :           GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     193              :                                        &ac->details.success.exchange_pub),
     194            2 :           GNUNET_JSON_spec_end ()
     195              :         };
     196              : 
     197            2 :         ac->type = TALER_MERCHANT_POAC_SUCCESS;
     198            2 :         if (GNUNET_OK !=
     199            2 :             GNUNET_JSON_parse (refund,
     200              :                                spec_detail,
     201              :                                NULL, NULL))
     202              :         {
     203            0 :           GNUNET_break_op (0);
     204            0 :           return GNUNET_SYSERR;
     205              :         }
     206            2 :         if (MHD_HTTP_OK != ac->details.success.exchange_status)
     207              :         {
     208            0 :           GNUNET_break_op (0);
     209            0 :           return GNUNET_SYSERR;
     210              :         }
     211              : 
     212            2 :         if (GNUNET_OK !=
     213            2 :             TALER_exchange_online_refund_confirmation_verify (
     214            2 :               &poah->h_contract,
     215            2 :               &poah->coins[i].coin_pub,
     216            2 :               &poah->merchant_pub,
     217              :               0,   /* transaction id */
     218            2 :               &poah->coins[i].amount_with_fee,
     219            2 :               &ac->details.success.exchange_pub,
     220            2 :               &ac->details.success.exchange_sig))
     221              :         {
     222            0 :           GNUNET_break_op (0);
     223            0 :           return GNUNET_SYSERR;
     224              :         }
     225            2 :         continue;
     226              :       }
     227            0 :       if (0 == strcmp (type,
     228              :                        "failure"))
     229            0 :       {
     230              :         struct GNUNET_JSON_Specification spec_detail[] = {
     231            0 :           GNUNET_JSON_spec_uint32 ("exchange_status",
     232              :                                    &ac->details.failure.exchange_status),
     233            0 :           TALER_JSON_spec_ec ("exchange_code",
     234              :                               &ac->details.failure.exchange_code),
     235            0 :           GNUNET_JSON_spec_mark_optional (
     236              :             GNUNET_JSON_spec_object_const ("exchange_reply",
     237              :                                            &ac->details.failure.exchange_reply),
     238              :             NULL),
     239            0 :           GNUNET_JSON_spec_end ()
     240              :         };
     241              : 
     242            0 :         ac->type = TALER_MERCHANT_POAC_FAILURE;
     243            0 :         if (GNUNET_OK !=
     244            0 :             GNUNET_JSON_parse (refund,
     245              :                                spec_detail,
     246              :                                NULL, NULL))
     247              :         {
     248            0 :           GNUNET_break_op (0);
     249            0 :           return GNUNET_SYSERR;
     250              :         }
     251            0 :         continue;
     252              :       }
     253            0 :       GNUNET_break_op (0);
     254            0 :       return GNUNET_SYSERR;
     255              :     }
     256            2 :     switch (ar->hr.http_status)
     257              :     {
     258            2 :     case MHD_HTTP_OK:
     259            2 :       ar->details.ok.num_aborts = num_refunds;
     260            2 :       ar->details.ok.aborts = res;
     261            2 :       break;
     262            0 :     case MHD_HTTP_BAD_GATEWAY:
     263            0 :       ar->details.bad_gateway.num_aborts = num_refunds;
     264            0 :       ar->details.bad_gateway.aborts = res;
     265            0 :       break;
     266            0 :     default:
     267            0 :       GNUNET_assert (0);
     268              :     }
     269            2 :     poah->cb (poah->cb_cls,
     270              :               ar);
     271            2 :     poah->cb = NULL;
     272              :   }
     273            2 :   return GNUNET_OK;
     274              : }
     275              : 
     276              : 
     277              : /**
     278              :  * Function called when we're done processing the
     279              :  * HTTP POST /orders/$ID/abort request.
     280              :  *
     281              :  * @param cls the `struct TALER_MERCHANT_PostOrdersAbortHandle`
     282              :  * @param response_code HTTP response code, 0 on error
     283              :  * @param response response body, NULL if not in JSON
     284              :  */
     285              : static void
     286            2 : handle_abort_finished (void *cls,
     287              :                        long response_code,
     288              :                        const void *response)
     289              : {
     290            2 :   struct TALER_MERCHANT_PostOrdersAbortHandle *poah = cls;
     291            2 :   const json_t *json = response;
     292            2 :   struct TALER_MERCHANT_PostOrdersAbortResponse ar = {
     293            2 :     .hr.http_status = (unsigned int) response_code,
     294              :     .hr.reply = json
     295              :   };
     296              : 
     297            2 :   poah->job = NULL;
     298            2 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     299              :               "POST /orders/$ID/abort completed with response code %u\n",
     300              :               (unsigned int) response_code);
     301            2 :   switch (response_code)
     302              :   {
     303            0 :   case 0:
     304            0 :     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     305            0 :     break;
     306            2 :   case MHD_HTTP_OK:
     307            2 :     if (GNUNET_OK ==
     308            2 :         check_abort_refund (poah,
     309              :                             &ar,
     310              :                             json))
     311              :     {
     312            2 :       TALER_MERCHANT_post_orders_abort_cancel (poah);
     313            2 :       return;
     314              :     }
     315            0 :     ar.hr.http_status = 0;
     316            0 :     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     317            0 :     break;
     318            0 :   case MHD_HTTP_BAD_REQUEST:
     319            0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     320            0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     321            0 :     break;
     322            0 :   case MHD_HTTP_FORBIDDEN:
     323            0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     324            0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     325            0 :     break;
     326            0 :   case MHD_HTTP_NOT_FOUND:
     327            0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     328            0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     329            0 :     break;
     330            0 :   case MHD_HTTP_REQUEST_TIMEOUT:
     331            0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     332            0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     333            0 :     break;
     334            0 :   case MHD_HTTP_PRECONDITION_FAILED:
     335            0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     336            0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     337            0 :     break;
     338            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     339            0 :     ar.hr.ec = TALER_JSON_get_error_code (json);
     340            0 :     ar.hr.hint = TALER_JSON_get_error_hint (json);
     341            0 :     break;
     342            0 :   case MHD_HTTP_BAD_GATEWAY:
     343            0 :     if (GNUNET_OK ==
     344            0 :         check_abort_refund (poah,
     345              :                             &ar,
     346              :                             json))
     347              :     {
     348            0 :       TALER_MERCHANT_post_orders_abort_cancel (poah);
     349            0 :       return;
     350              :     }
     351            0 :     ar.hr.http_status = 0;
     352            0 :     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     353            0 :     break;
     354            0 :   case MHD_HTTP_GATEWAY_TIMEOUT:
     355            0 :     TALER_MERCHANT_parse_error_details_ (json,
     356              :                                          response_code,
     357              :                                          &ar.hr);
     358            0 :     ar.details.gateway_timeout.exchange_ec = ar.hr.exchange_code;
     359              :     ar.details.gateway_timeout.exchange_http_status
     360            0 :       = ar.hr.exchange_http_status;
     361            0 :     break;
     362            0 :   default:
     363            0 :     TALER_MERCHANT_parse_error_details_ (json,
     364              :                                          response_code,
     365              :                                          &ar.hr);
     366            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     367              :                 "Unexpected response code %u/%d\n",
     368              :                 (unsigned int) response_code,
     369              :                 (int) ar.hr.ec);
     370            0 :     GNUNET_break_op (0);
     371            0 :     break;
     372              :   }
     373            0 :   poah->cb (poah->cb_cls,
     374              :             &ar);
     375            0 :   TALER_MERCHANT_post_orders_abort_cancel (poah);
     376              : }
     377              : 
     378              : 
     379              : struct TALER_MERCHANT_PostOrdersAbortHandle *
     380            2 : TALER_MERCHANT_post_orders_abort_create (
     381              :   struct GNUNET_CURL_Context *ctx,
     382              :   const char *url,
     383              :   const char *order_id,
     384              :   const struct TALER_MerchantPublicKeyP *merchant_pub,
     385              :   const struct TALER_PrivateContractHashP *h_contract,
     386              :   unsigned int num_coins,
     387              :   const struct TALER_MERCHANT_PostOrdersAbortCoin coins[static num_coins])
     388            2 : {
     389              :   struct TALER_MERCHANT_PostOrdersAbortHandle *poah;
     390              : 
     391            2 :   if (MAX_REFUNDS < num_coins)
     392              :   {
     393            0 :     GNUNET_break (0);
     394            0 :     return NULL;
     395              :   }
     396            2 :   poah = GNUNET_new (struct TALER_MERCHANT_PostOrdersAbortHandle);
     397            2 :   poah->ctx = ctx;
     398            2 :   poah->base_url = GNUNET_strdup (url);
     399            2 :   poah->order_id = GNUNET_strdup (order_id);
     400            2 :   poah->merchant_pub = *merchant_pub;
     401            2 :   poah->h_contract = *h_contract;
     402            2 :   poah->num_coins = num_coins;
     403            2 :   poah->coins = GNUNET_new_array (num_coins,
     404              :                                   struct TALER_MERCHANT_PostOrdersAbortCoin);
     405            2 :   GNUNET_memcpy (
     406              :     poah->coins,
     407              :     coins,
     408              :     num_coins * sizeof (struct TALER_MERCHANT_PostOrdersAbortCoin));
     409            4 :   for (unsigned int i = 0; i<num_coins; i++)
     410            2 :     poah->coins[i].exchange_url
     411            2 :       = GNUNET_strdup (poah->coins[i].exchange_url);
     412            2 :   return poah;
     413              : }
     414              : 
     415              : 
     416              : enum TALER_ErrorCode
     417            2 : TALER_MERCHANT_post_orders_abort_start (
     418              :   struct TALER_MERCHANT_PostOrdersAbortHandle *poah,
     419              :   TALER_MERCHANT_PostOrdersAbortCallback cb,
     420              :   TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls)
     421              : {
     422              :   json_t *abort_obj;
     423              :   json_t *j_coins;
     424              :   CURL *eh;
     425              : 
     426            2 :   poah->cb = cb;
     427            2 :   poah->cb_cls = cb_cls;
     428              :   {
     429              :     char *path;
     430              : 
     431            2 :     GNUNET_asprintf (&path,
     432              :                      "orders/%s/abort",
     433              :                      poah->order_id);
     434            2 :     poah->url = TALER_url_join (poah->base_url,
     435              :                                 path,
     436              :                                 NULL);
     437            2 :     GNUNET_free (path);
     438              :   }
     439            2 :   if (NULL == poah->url)
     440            0 :     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
     441            2 :   j_coins = json_array ();
     442            2 :   if (NULL == j_coins)
     443              :   {
     444            0 :     GNUNET_break (0);
     445            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     446              :   }
     447            4 :   for (unsigned int i = 0; i<poah->num_coins; i++)
     448              :   {
     449            2 :     const struct TALER_MERCHANT_PostOrdersAbortCoin *ac = &poah->coins[i];
     450              :     json_t *j_coin;
     451              : 
     452            2 :     j_coin = GNUNET_JSON_PACK (
     453              :       GNUNET_JSON_pack_data_auto ("coin_pub",
     454              :                                   &ac->coin_pub),
     455              :       GNUNET_JSON_pack_string ("exchange_url",
     456              :                                ac->exchange_url));
     457            2 :     if (0 !=
     458            2 :         json_array_append_new (j_coins,
     459              :                                j_coin))
     460              :     {
     461            0 :       GNUNET_break (0);
     462            0 :       json_decref (j_coins);
     463            0 :       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     464              :     }
     465              :   }
     466            2 :   abort_obj = GNUNET_JSON_PACK (
     467              :     GNUNET_JSON_pack_array_steal ("coins",
     468              :                                   j_coins),
     469              :     GNUNET_JSON_pack_data_auto ("h_contract",
     470              :                                 &poah->h_contract));
     471            2 :   eh = TALER_MERCHANT_curl_easy_get_ (poah->url);
     472            4 :   if ( (NULL == eh) ||
     473              :        (GNUNET_OK !=
     474            2 :         TALER_curl_easy_post (&poah->post_ctx,
     475              :                               eh,
     476              :                               abort_obj)) )
     477              :   {
     478            0 :     GNUNET_break (0);
     479            0 :     json_decref (abort_obj);
     480            0 :     if (NULL != eh)
     481            0 :       curl_easy_cleanup (eh);
     482            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     483              :   }
     484            2 :   json_decref (abort_obj);
     485            4 :   poah->job = GNUNET_CURL_job_add2 (poah->ctx,
     486              :                                     eh,
     487            2 :                                     poah->post_ctx.headers,
     488              :                                     &handle_abort_finished,
     489              :                                     poah);
     490            2 :   if (NULL == poah->job)
     491            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     492            2 :   return TALER_EC_NONE;
     493              : }
     494              : 
     495              : 
     496              : void
     497            2 : TALER_MERCHANT_post_orders_abort_cancel (
     498              :   struct TALER_MERCHANT_PostOrdersAbortHandle *poah)
     499              : {
     500            2 :   if (NULL != poah->job)
     501              :   {
     502            0 :     GNUNET_CURL_job_cancel (poah->job);
     503            0 :     poah->job = NULL;
     504              :   }
     505            2 :   TALER_curl_easy_post_finished (&poah->post_ctx);
     506            4 :   for (unsigned int i = 0; i < poah->num_coins; i++)
     507            2 :     GNUNET_free_nz ((char *) poah->coins[i].exchange_url);
     508            2 :   GNUNET_free (poah->coins);
     509            2 :   GNUNET_free (poah->order_id);
     510            2 :   GNUNET_free (poah->url);
     511            2 :   GNUNET_free (poah->base_url);
     512            2 :   GNUNET_free (poah);
     513            2 : }
     514              : 
     515              : 
     516              : /* end of merchant_api_post-orders-ORDER_ID-abort-new.c */
        

Generated by: LCOV version 2.0-1