LCOV - code coverage report
Current view: top level - lib - merchant_api_post_order_abort.c (source / functions) Hit Total Coverage
Test: GNU Taler coverage report Lines: 0 161 0.0 %
Date: 2020-08-15 06:12:35 Functions: 0 4 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014, 2015, 2016, 2017, 2020 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 lib/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 <taler/taler_json_lib.h>
      34             : #include <taler/taler_signatures.h>
      35             : #include <taler/taler_exchange_service.h>
      36             : #include <taler/taler_curl_lib.h>
      37             : 
      38             : 
      39             : /**
      40             :  * @brief An abort Handle
      41             :  */
      42             : struct TALER_MERCHANT_OrderAbortHandle
      43             : {
      44             :   /**
      45             :    * Hash of the contract.
      46             :    */
      47             :   struct GNUNET_HashCode h_contract_terms;
      48             : 
      49             :   /**
      50             :    * Public key of the merchant.
      51             :    */
      52             :   struct TALER_MerchantPublicKeyP merchant_pub;
      53             : 
      54             :   /**
      55             :    * The 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_AbortCallback abort_cb;
      68             : 
      69             :   /**
      70             :    * Closure for @a abort_cb.
      71             :    */
      72             :   void *abort_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             :    * The coins we are aborting on.
      86             :    */
      87             :   struct TALER_MERCHANT_AbortCoin *coins;
      88             : 
      89             :   /**
      90             :    * Number of @e coins we are paying with.
      91             :    */
      92             :   unsigned int num_coins;
      93             : 
      94             : };
      95             : 
      96             : 
      97             : /**
      98             :  * Check that the response for an abort is well-formed,
      99             :  * and call the application callback with the result if it is
     100             :  * OK. Otherwise returns #GNUNET_SYSERR.
     101             :  *
     102             :  * @param oah handle to operation that created the reply
     103             :  * @param json the reply to parse
     104             :  * @return #GNUNET_OK on success
     105             :  */
     106             : static int
     107           0 : check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
     108             :                     const json_t *json)
     109             : {
     110             :   json_t *refunds;
     111             :   unsigned int num_refunds;
     112             :   struct GNUNET_JSON_Specification spec[] = {
     113           0 :     GNUNET_JSON_spec_json ("refunds", &refunds),
     114           0 :     GNUNET_JSON_spec_end ()
     115             :   };
     116             : 
     117           0 :   if (GNUNET_OK !=
     118           0 :       GNUNET_JSON_parse (json,
     119             :                          spec,
     120             :                          NULL, NULL))
     121             :   {
     122           0 :     GNUNET_break_op (0);
     123           0 :     return GNUNET_SYSERR;
     124             :   }
     125           0 :   if (! json_is_array (refunds))
     126             :   {
     127           0 :     GNUNET_break_op (0);
     128           0 :     GNUNET_JSON_parse_free (spec);
     129           0 :     return GNUNET_SYSERR;
     130             :   }
     131           0 :   num_refunds = json_array_size (refunds);
     132           0 :   {
     133           0 :     struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)];
     134             : 
     135           0 :     for (unsigned int i = 0; i<num_refunds; i++)
     136             :     {
     137           0 :       json_t *refund = json_array_get (refunds, i);
     138             :       uint32_t exchange_status;
     139             :       json_t *exchange_reply;
     140             :       struct GNUNET_JSON_Specification spec_es[] = {
     141           0 :         GNUNET_JSON_spec_uint32 ("exchange_http_status",
     142             :                                  &exchange_status),
     143           0 :         GNUNET_JSON_spec_json ("exchange_reply",
     144             :                                &exchange_reply),
     145           0 :         GNUNET_JSON_spec_end ()
     146             :       };
     147             : 
     148           0 :       if (GNUNET_OK !=
     149           0 :           GNUNET_JSON_parse (refund,
     150             :                              spec_es,
     151             :                              NULL, NULL))
     152             :       {
     153           0 :         GNUNET_break_op (0);
     154           0 :         GNUNET_JSON_parse_free (spec);
     155           0 :         return GNUNET_SYSERR;
     156             :       }
     157           0 :       if (MHD_HTTP_OK == exchange_status)
     158             :       {
     159             :         struct GNUNET_JSON_Specification spec_detail[] = {
     160           0 :           GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     161             :                                        &res[i].exchange_sig),
     162           0 :           GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     163             :                                        &res[i].exchange_pub),
     164           0 :           GNUNET_JSON_spec_end ()
     165             :         };
     166             : 
     167           0 :         if (GNUNET_OK !=
     168           0 :             GNUNET_JSON_parse (exchange_reply,
     169             :                                spec_detail,
     170             :                                NULL, NULL))
     171             :         {
     172           0 :           GNUNET_break_op (0);
     173           0 :           GNUNET_JSON_parse_free (spec);
     174           0 :           return GNUNET_SYSERR;
     175             :         }
     176             :       }
     177             : 
     178             :       {
     179           0 :         struct TALER_RefundConfirmationPS rr = {
     180           0 :           .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
     181           0 :           .purpose.size = htonl (sizeof (rr)),
     182             :           .h_contract_terms = oah->h_contract_terms,
     183           0 :           .coin_pub = oah->coins[i].coin_pub,
     184             :           .merchant = oah->merchant_pub,
     185           0 :           .rtransaction_id = GNUNET_htonll (0)
     186             :         };
     187             : 
     188           0 :         TALER_amount_hton (&rr.refund_amount,
     189           0 :                            &oah->coins[i].amount_with_fee);
     190           0 :         if (GNUNET_OK !=
     191           0 :             GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
     192             :                                         &rr,
     193             :                                         &res[i].exchange_sig.eddsa_signature,
     194             :                                         &res[i].exchange_pub.eddsa_pub))
     195             :         {
     196           0 :           GNUNET_break_op (0);
     197           0 :           GNUNET_JSON_parse_free (spec);
     198           0 :           return GNUNET_SYSERR;
     199             :         }
     200             :       }
     201             :     }
     202             :     {
     203           0 :       struct TALER_MERCHANT_HttpResponse hr = {
     204             :         .reply = json,
     205             :         .http_status = MHD_HTTP_OK
     206             :       };
     207             : 
     208           0 :       oah->abort_cb (oah->abort_cb_cls,
     209             :                      &hr,
     210           0 :                      &oah->merchant_pub,
     211             :                      num_refunds,
     212             :                      res);
     213             :     }
     214           0 :     oah->abort_cb = NULL;
     215             :   }
     216           0 :   GNUNET_JSON_parse_free (spec);
     217           0 :   return GNUNET_OK;
     218             : }
     219             : 
     220             : 
     221             : /**
     222             :  * Function called when we're done processing the
     223             :  * abort request.
     224             :  *
     225             :  * @param cls the `struct TALER_MERCHANT_OrderAbortHandle`
     226             :  * @param response_code HTTP response code, 0 on error
     227             :  * @param json response body, NULL if not in JSON
     228             :  */
     229             : static void
     230           0 : handle_abort_finished (void *cls,
     231             :                        long response_code,
     232             :                        const void *response)
     233             : {
     234           0 :   struct TALER_MERCHANT_OrderAbortHandle *oah = cls;
     235           0 :   const json_t *json = response;
     236           0 :   struct TALER_MERCHANT_HttpResponse hr = {
     237           0 :     .http_status = (unsigned int) response_code,
     238             :     .reply = json
     239             :   };
     240             : 
     241           0 :   oah->job = NULL;
     242           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     243             :               "/pay completed with response code %u\n",
     244             :               (unsigned int) response_code);
     245           0 :   switch (response_code)
     246             :   {
     247           0 :   case 0:
     248           0 :     hr.ec = TALER_EC_INVALID_RESPONSE;
     249           0 :     break;
     250           0 :   case MHD_HTTP_OK:
     251           0 :     if (GNUNET_OK ==
     252           0 :         check_abort_refund (oah,
     253             :                             json))
     254             :     {
     255           0 :       TALER_MERCHANT_order_abort_cancel (oah);
     256           0 :       return;
     257             :     }
     258           0 :     hr.http_status = 0;
     259           0 :     hr.ec = TALER_EC_PAY_MERCHANT_INVALID_RESPONSE;
     260           0 :     break;
     261           0 :   case MHD_HTTP_BAD_REQUEST:
     262           0 :     hr.ec = TALER_JSON_get_error_code (json);
     263           0 :     hr.hint = TALER_JSON_get_error_hint (json);
     264             :     /* This should never happen, either us or the
     265             :        merchant is buggy (or API version conflict); just
     266             :        pass JSON reply to the application */
     267           0 :     break;
     268           0 :   case MHD_HTTP_FORBIDDEN:
     269           0 :     hr.ec = TALER_JSON_get_error_code (json);
     270           0 :     hr.hint = TALER_JSON_get_error_hint (json);
     271           0 :     break;
     272           0 :   case MHD_HTTP_NOT_FOUND:
     273           0 :     hr.ec = TALER_JSON_get_error_code (json);
     274           0 :     hr.hint = TALER_JSON_get_error_hint (json);
     275             :     /* Nothing really to verify, this should never
     276             :  happen, we should pass the JSON reply to the
     277             :        application */
     278           0 :     break;
     279           0 :   case MHD_HTTP_REQUEST_TIMEOUT:
     280           0 :     hr.ec = TALER_JSON_get_error_code (json);
     281           0 :     hr.hint = TALER_JSON_get_error_hint (json);
     282             :     /* Nothing really to verify, merchant says one of
     283             :        the signatures is invalid; as we checked them,
     284             :        this should never happen, we should pass the JSON
     285             :        reply to the application */
     286           0 :     break;
     287           0 :   case MHD_HTTP_PRECONDITION_FAILED:
     288             :     /* Our *payment* already succeeded fully. */
     289           0 :     hr.ec = TALER_JSON_get_error_code (json);
     290           0 :     hr.hint = TALER_JSON_get_error_hint (json);
     291           0 :     break;
     292           0 :   case MHD_HTTP_FAILED_DEPENDENCY:
     293           0 :     TALER_MERCHANT_parse_error_details_ (json,
     294             :                                          response_code,
     295             :                                          &hr);
     296             :     /* Nothing really to verify, the merchant is blaming the exchange.
     297             :        We should pass the JSON reply to the application */
     298           0 :     break;
     299           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     300           0 :     hr.ec = TALER_JSON_get_error_code (json);
     301           0 :     hr.hint = TALER_JSON_get_error_hint (json);
     302             :     /* Server had an internal issue; we should retry,
     303             :        but this API leaves this to the application */
     304           0 :     break;
     305           0 :   default:
     306             :     /* unexpected response code */
     307           0 :     TALER_MERCHANT_parse_error_details_ (json,
     308             :                                          response_code,
     309             :                                          &hr);
     310           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     311             :                 "Unexpected response code %u/%d\n",
     312             :                 (unsigned int) response_code,
     313             :                 (int) hr.ec);
     314           0 :     GNUNET_break_op (0);
     315           0 :     break;
     316             :   }
     317           0 :   oah->abort_cb (oah->abort_cb_cls,
     318             :                  &hr,
     319             :                  NULL,
     320             :                  0,
     321             :                  NULL);
     322           0 :   TALER_MERCHANT_order_abort_cancel (oah);
     323             : }
     324             : 
     325             : 
     326             : /**
     327             :  * Run an abort operation, asking for refunds for coins
     328             :  * that were previously spend on a/pay that failed to go through.
     329             :  *
     330             :  * @param ctx execution context
     331             :  * @param merchant_url base URL of the merchant
     332             :  * @param order_id order to abort
     333             :  * @param h_contract hash of the contact of the merchant with the customer
     334             :  * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
     335             :  * @param num_coins number of coins used to pay
     336             :  * @param coins array of coins we use to pay
     337             :  * @param cb the callback to call when a reply for this request is available
     338             :  * @param cb_cls closure for @a pay_cb
     339             :  * @return a handle for this request
     340             :  */
     341             : struct TALER_MERCHANT_OrderAbortHandle *
     342           0 : TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
     343             :                             const char *merchant_url,
     344             :                             const char *order_id,
     345             :                             const struct TALER_MerchantPublicKeyP *merchant_pub,
     346             :                             const struct GNUNET_HashCode *h_contract,
     347             :                             unsigned int num_coins,
     348             :                             const struct TALER_MERCHANT_AbortCoin coins[],
     349             :                             TALER_MERCHANT_AbortCallback cb,
     350             :                             void *cb_cls)
     351             : {
     352             :   struct TALER_MERCHANT_OrderAbortHandle *oah;
     353             :   json_t *abort_obj;
     354             :   json_t *j_coins;
     355             : 
     356           0 :   j_coins = json_array ();
     357           0 :   if (NULL == j_coins)
     358             :   {
     359           0 :     GNUNET_break (0);
     360           0 :     return NULL;
     361             :   }
     362           0 :   for (unsigned int i = 0; i<num_coins; i++)
     363             :   {
     364           0 :     const struct TALER_MERCHANT_AbortCoin *ac = &coins[i];
     365             :     json_t *j_coin;
     366             : 
     367             :     /* create JSON for this coin */
     368           0 :     j_coin = json_pack (
     369             :       "{s:o, s:o,s:s}",
     370             :       "coin_pub",
     371           0 :       GNUNET_JSON_from_data_auto (&ac->coin_pub),
     372             :       "contribution",
     373             :       TALER_JSON_from_amount (&ac->amount_with_fee),
     374             :       "exchange_url",
     375             :       ac->exchange_url);
     376           0 :     if ( (NULL == j_coin) ||
     377             :          (0 !=
     378           0 :           json_array_append_new (j_coins,
     379             :                                  j_coin)) )
     380             :     {
     381           0 :       GNUNET_break (0);
     382           0 :       json_decref (j_coins);
     383           0 :       return NULL;
     384             :     }
     385             :   }
     386           0 :   abort_obj = json_pack ("{s:o,s:o}",
     387             :                          "coins",
     388             :                          j_coins, /* reference consumed! */
     389             :                          "h_contract",
     390             :                          GNUNET_JSON_from_data_auto (h_contract));
     391           0 :   if (NULL == abort_obj)
     392             :   {
     393           0 :     GNUNET_break (0);
     394           0 :     return NULL;
     395             :   }
     396           0 :   oah = GNUNET_new (struct TALER_MERCHANT_OrderAbortHandle);
     397           0 :   oah->h_contract_terms = *h_contract;
     398           0 :   oah->merchant_pub = *merchant_pub;
     399           0 :   oah->ctx = ctx;
     400           0 :   oah->abort_cb = cb;
     401           0 :   oah->abort_cb_cls = cb_cls;
     402             :   {
     403             :     char *path;
     404             : 
     405           0 :     GNUNET_asprintf (&path,
     406             :                      "orders/%s/abort",
     407             :                      order_id);
     408           0 :     oah->url = TALER_url_join (merchant_url,
     409             :                                path,
     410             :                                NULL);
     411           0 :     GNUNET_free (path);
     412             :   }
     413           0 :   if (NULL == oah->url)
     414             :   {
     415           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     416             :                 "Could not construct request URL.\n");
     417           0 :     json_decref (abort_obj);
     418           0 :     GNUNET_free (oah);
     419           0 :     return NULL;
     420             :   }
     421           0 :   oah->num_coins = num_coins;
     422           0 :   oah->coins = GNUNET_new_array (num_coins,
     423             :                                  struct TALER_MERCHANT_AbortCoin);
     424           0 :   memcpy (oah->coins,
     425             :           coins,
     426             :           num_coins * sizeof (struct TALER_MERCHANT_AbortCoin));
     427             :   {
     428             :     CURL *eh;
     429             : 
     430           0 :     eh = curl_easy_init ();
     431           0 :     GNUNET_assert (NULL != eh);
     432           0 :     if (GNUNET_OK !=
     433           0 :         TALER_curl_easy_post (&oah->post_ctx,
     434             :                               eh,
     435             :                               abort_obj))
     436             :     {
     437           0 :       GNUNET_break (0);
     438           0 :       json_decref (abort_obj);
     439           0 :       GNUNET_free (oah);
     440           0 :       return NULL;
     441             :     }
     442           0 :     json_decref (abort_obj);
     443           0 :     GNUNET_assert (CURLE_OK ==
     444             :                    curl_easy_setopt (eh,
     445             :                                      CURLOPT_URL,
     446             :                                      oah->url));
     447           0 :     oah->job = GNUNET_CURL_job_add2 (ctx,
     448             :                                      eh,
     449           0 :                                      oah->post_ctx.headers,
     450             :                                      &handle_abort_finished,
     451             :                                      oah);
     452             :   }
     453           0 :   return oah;
     454             : }
     455             : 
     456             : 
     457             : /**
     458             :  * Cancel an abort request.  This function cannot be used on a request handle
     459             :  * if a response is already served for it.
     460             :  *
     461             :  * @param oah the pay permission request handle
     462             :  */
     463             : void
     464           0 : TALER_MERCHANT_order_abort_cancel (struct TALER_MERCHANT_OrderAbortHandle *oah)
     465             : {
     466           0 :   if (NULL != oah->job)
     467             :   {
     468           0 :     GNUNET_CURL_job_cancel (oah->job);
     469           0 :     oah->job = NULL;
     470             :   }
     471           0 :   TALER_curl_easy_post_finished (&oah->post_ctx);
     472           0 :   GNUNET_free (oah->coins);
     473           0 :   GNUNET_free (oah->url);
     474           0 :   GNUNET_free (oah);
     475           0 : }
     476             : 
     477             : 
     478             : /* end of merchant_api_post_order_abort.c */

Generated by: LCOV version 1.14