merchant

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

merchant_api_post-orders-ORDER_ID-abort.c (13036B)


      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 merchant_api_post-orders-ORDER_ID-abort-new.c
     21  * @brief Implementation of the POST /orders/$ID/abort request
     22  * @author Christian Grothoff
     23  */
     24 #include "taler/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/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 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     GNUNET_JSON_spec_array_const ("refunds",
    130                                   &refunds),
    131     GNUNET_JSON_spec_end ()
    132   };
    133 
    134   if (GNUNET_OK !=
    135       GNUNET_JSON_parse (json,
    136                          spec,
    137                          NULL, NULL))
    138   {
    139     GNUNET_break_op (0);
    140     return GNUNET_SYSERR;
    141   }
    142   num_refunds = (unsigned int) json_array_size (refunds);
    143   if ( (json_array_size (refunds) != (size_t) num_refunds) ||
    144        (num_refunds > MAX_REFUNDS) )
    145   {
    146     GNUNET_break (0);
    147     return GNUNET_SYSERR;
    148   }
    149 
    150   {
    151     struct TALER_MERCHANT_PostOrdersAbortedCoin res[GNUNET_NZL (num_refunds)];
    152 
    153     for (unsigned int i = 0; i<num_refunds; i++)
    154     {
    155       json_t *refund = json_array_get (refunds, i);
    156       uint32_t exchange_status;
    157       struct GNUNET_JSON_Specification spec_es[] = {
    158         GNUNET_JSON_spec_uint32 ("exchange_status",
    159                                  &exchange_status),
    160         GNUNET_JSON_spec_end ()
    161       };
    162 
    163       if (GNUNET_OK !=
    164           GNUNET_JSON_parse (refund,
    165                              spec_es,
    166                              NULL, NULL))
    167       {
    168         GNUNET_break_op (0);
    169         return GNUNET_SYSERR;
    170       }
    171       if (MHD_HTTP_OK == exchange_status)
    172       {
    173         struct GNUNET_JSON_Specification spec_detail[] = {
    174           GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    175                                        &res[i].exchange_sig),
    176           GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    177                                        &res[i].exchange_pub),
    178           GNUNET_JSON_spec_end ()
    179         };
    180 
    181         if (GNUNET_OK !=
    182             GNUNET_JSON_parse (refund,
    183                                spec_detail,
    184                                NULL, NULL))
    185         {
    186           GNUNET_break_op (0);
    187           return GNUNET_SYSERR;
    188         }
    189         res[i].coin_pub = poah->coins[i].coin_pub;
    190 
    191         if (GNUNET_OK !=
    192             TALER_exchange_online_refund_confirmation_verify (
    193               &poah->h_contract,
    194               &poah->coins[i].coin_pub,
    195               &poah->merchant_pub,
    196               0,   /* transaction id */
    197               &poah->coins[i].amount_with_fee,
    198               &res[i].exchange_pub,
    199               &res[i].exchange_sig))
    200         {
    201           GNUNET_break_op (0);
    202           return GNUNET_SYSERR;
    203         }
    204       }
    205     }
    206     switch (ar->hr.http_status)
    207     {
    208     case MHD_HTTP_OK:
    209       ar->details.ok.num_aborts = num_refunds;
    210       ar->details.ok.aborts = res;
    211       break;
    212     case MHD_HTTP_BAD_GATEWAY:
    213       ar->details.bad_gateway.num_aborts = num_refunds;
    214       ar->details.bad_gateway.aborts = res;
    215       break;
    216     default:
    217       GNUNET_assert (0);
    218     }
    219     poah->cb (poah->cb_cls,
    220               ar);
    221     poah->cb = NULL;
    222   }
    223   return GNUNET_OK;
    224 }
    225 
    226 
    227 /**
    228  * Function called when we're done processing the
    229  * HTTP POST /orders/$ID/abort request.
    230  *
    231  * @param cls the `struct TALER_MERCHANT_PostOrdersAbortHandle`
    232  * @param response_code HTTP response code, 0 on error
    233  * @param response response body, NULL if not in JSON
    234  */
    235 static void
    236 handle_abort_finished (void *cls,
    237                        long response_code,
    238                        const void *response)
    239 {
    240   struct TALER_MERCHANT_PostOrdersAbortHandle *poah = cls;
    241   const json_t *json = response;
    242   struct TALER_MERCHANT_PostOrdersAbortResponse ar = {
    243     .hr.http_status = (unsigned int) response_code,
    244     .hr.reply = json
    245   };
    246 
    247   poah->job = NULL;
    248   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    249               "POST /orders/$ID/abort completed with response code %u\n",
    250               (unsigned int) response_code);
    251   switch (response_code)
    252   {
    253   case 0:
    254     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    255     break;
    256   case MHD_HTTP_OK:
    257     if (GNUNET_OK ==
    258         check_abort_refund (poah,
    259                             &ar,
    260                             json))
    261     {
    262       TALER_MERCHANT_post_orders_abort_cancel (poah);
    263       return;
    264     }
    265     ar.hr.http_status = 0;
    266     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    267     break;
    268   case MHD_HTTP_BAD_REQUEST:
    269     ar.hr.ec = TALER_JSON_get_error_code (json);
    270     ar.hr.hint = TALER_JSON_get_error_hint (json);
    271     break;
    272   case MHD_HTTP_FORBIDDEN:
    273     ar.hr.ec = TALER_JSON_get_error_code (json);
    274     ar.hr.hint = TALER_JSON_get_error_hint (json);
    275     break;
    276   case MHD_HTTP_NOT_FOUND:
    277     ar.hr.ec = TALER_JSON_get_error_code (json);
    278     ar.hr.hint = TALER_JSON_get_error_hint (json);
    279     break;
    280   case MHD_HTTP_REQUEST_TIMEOUT:
    281     ar.hr.ec = TALER_JSON_get_error_code (json);
    282     ar.hr.hint = TALER_JSON_get_error_hint (json);
    283     break;
    284   case MHD_HTTP_PRECONDITION_FAILED:
    285     ar.hr.ec = TALER_JSON_get_error_code (json);
    286     ar.hr.hint = TALER_JSON_get_error_hint (json);
    287     break;
    288   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    289     ar.hr.ec = TALER_JSON_get_error_code (json);
    290     ar.hr.hint = TALER_JSON_get_error_hint (json);
    291     break;
    292   case MHD_HTTP_BAD_GATEWAY:
    293     if (GNUNET_OK ==
    294         check_abort_refund (poah,
    295                             &ar,
    296                             json))
    297     {
    298       TALER_MERCHANT_post_orders_abort_cancel (poah);
    299       return;
    300     }
    301     ar.hr.http_status = 0;
    302     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    303     break;
    304   case MHD_HTTP_GATEWAY_TIMEOUT:
    305     TALER_MERCHANT_parse_error_details_ (json,
    306                                          response_code,
    307                                          &ar.hr);
    308     ar.details.gateway_timeout.exchange_ec = ar.hr.exchange_code;
    309     ar.details.gateway_timeout.exchange_http_status
    310       = ar.hr.exchange_http_status;
    311     break;
    312   default:
    313     TALER_MERCHANT_parse_error_details_ (json,
    314                                          response_code,
    315                                          &ar.hr);
    316     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    317                 "Unexpected response code %u/%d\n",
    318                 (unsigned int) response_code,
    319                 (int) ar.hr.ec);
    320     GNUNET_break_op (0);
    321     break;
    322   }
    323   poah->cb (poah->cb_cls,
    324             &ar);
    325   TALER_MERCHANT_post_orders_abort_cancel (poah);
    326 }
    327 
    328 
    329 struct TALER_MERCHANT_PostOrdersAbortHandle *
    330 TALER_MERCHANT_post_orders_abort_create (
    331   struct GNUNET_CURL_Context *ctx,
    332   const char *url,
    333   const char *order_id,
    334   const struct TALER_MerchantPublicKeyP *merchant_pub,
    335   const struct TALER_PrivateContractHashP *h_contract,
    336   unsigned int num_coins,
    337   const struct TALER_MERCHANT_PostOrdersAbortCoin coins[static num_coins])
    338 {
    339   struct TALER_MERCHANT_PostOrdersAbortHandle *poah;
    340 
    341   poah = GNUNET_new (struct TALER_MERCHANT_PostOrdersAbortHandle);
    342   poah->ctx = ctx;
    343   poah->base_url = GNUNET_strdup (url);
    344   poah->order_id = GNUNET_strdup (order_id);
    345   poah->merchant_pub = *merchant_pub;
    346   poah->h_contract = *h_contract;
    347   poah->num_coins = num_coins;
    348   poah->coins = GNUNET_new_array (num_coins,
    349                                   struct TALER_MERCHANT_PostOrdersAbortCoin);
    350   GNUNET_memcpy (
    351     poah->coins,
    352     coins,
    353     num_coins * sizeof (struct TALER_MERCHANT_PostOrdersAbortCoin));
    354   return poah;
    355 }
    356 
    357 
    358 enum TALER_ErrorCode
    359 TALER_MERCHANT_post_orders_abort_start (
    360   struct TALER_MERCHANT_PostOrdersAbortHandle *poah,
    361   TALER_MERCHANT_PostOrdersAbortCallback cb,
    362   TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls)
    363 {
    364   json_t *abort_obj;
    365   json_t *j_coins;
    366   CURL *eh;
    367 
    368   poah->cb = cb;
    369   poah->cb_cls = cb_cls;
    370   {
    371     char *path;
    372 
    373     GNUNET_asprintf (&path,
    374                      "orders/%s/abort",
    375                      poah->order_id);
    376     poah->url = TALER_url_join (poah->base_url,
    377                                 path,
    378                                 NULL);
    379     GNUNET_free (path);
    380   }
    381   if (NULL == poah->url)
    382     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    383   j_coins = json_array ();
    384   if (NULL == j_coins)
    385   {
    386     GNUNET_break (0);
    387     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    388   }
    389   for (unsigned int i = 0; i<poah->num_coins; i++)
    390   {
    391     const struct TALER_MERCHANT_PostOrdersAbortCoin *ac = &poah->coins[i];
    392     json_t *j_coin;
    393 
    394     j_coin = GNUNET_JSON_PACK (
    395       GNUNET_JSON_pack_data_auto ("coin_pub",
    396                                   &ac->coin_pub),
    397       TALER_JSON_pack_amount ("contribution",
    398                               &ac->amount_with_fee),
    399       GNUNET_JSON_pack_string ("exchange_url",
    400                                ac->exchange_url));
    401     if (0 !=
    402         json_array_append_new (j_coins,
    403                                j_coin))
    404     {
    405       GNUNET_break (0);
    406       json_decref (j_coins);
    407       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    408     }
    409   }
    410   abort_obj = GNUNET_JSON_PACK (
    411     GNUNET_JSON_pack_array_steal ("coins",
    412                                   j_coins),
    413     GNUNET_JSON_pack_data_auto ("h_contract",
    414                                 &poah->h_contract));
    415   eh = TALER_MERCHANT_curl_easy_get_ (poah->url);
    416   if ( (NULL == eh) ||
    417        (GNUNET_OK !=
    418         TALER_curl_easy_post (&poah->post_ctx,
    419                               eh,
    420                               abort_obj)) )
    421   {
    422     GNUNET_break (0);
    423     json_decref (abort_obj);
    424     if (NULL != eh)
    425       curl_easy_cleanup (eh);
    426     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    427   }
    428   json_decref (abort_obj);
    429   poah->job = GNUNET_CURL_job_add2 (poah->ctx,
    430                                     eh,
    431                                     poah->post_ctx.headers,
    432                                     &handle_abort_finished,
    433                                     poah);
    434   if (NULL == poah->job)
    435     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    436   return TALER_EC_NONE;
    437 }
    438 
    439 
    440 void
    441 TALER_MERCHANT_post_orders_abort_cancel (
    442   struct TALER_MERCHANT_PostOrdersAbortHandle *poah)
    443 {
    444   if (NULL != poah->job)
    445   {
    446     GNUNET_CURL_job_cancel (poah->job);
    447     poah->job = NULL;
    448   }
    449   TALER_curl_easy_post_finished (&poah->post_ctx);
    450   GNUNET_free (poah->coins);
    451   GNUNET_free (poah->order_id);
    452   GNUNET_free (poah->url);
    453   GNUNET_free (poah->base_url);
    454   GNUNET_free (poah);
    455 }
    456 
    457 
    458 /* end of merchant_api_post-orders-ORDER_ID-abort-new.c */