merchant

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

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


      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 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 != poah->num_coins) )
    145   {
    146     GNUNET_break (0);
    147     return GNUNET_SYSERR;
    148   }
    149 
    150   {
    151     struct TALER_MERCHANT_PostOrdersAbortedCoin res[GNUNET_NZL (num_refunds)];
    152 
    153     memset (res,
    154             0,
    155             sizeof (res));
    156     for (unsigned int i = 0; i<num_refunds; i++)
    157     {
    158       json_t *refund = json_array_get (refunds, i);
    159       struct TALER_MERCHANT_PostOrdersAbortedCoin *ac = &res[i];
    160       const char *type;
    161       struct GNUNET_JSON_Specification spec_type[] = {
    162         GNUNET_JSON_spec_string ("type",
    163                                  &type),
    164         GNUNET_JSON_spec_end ()
    165       };
    166 
    167       ac->coin_pub = poah->coins[i].coin_pub;
    168       if (GNUNET_OK !=
    169           GNUNET_JSON_parse (refund,
    170                              spec_type,
    171                              NULL, NULL))
    172       {
    173         GNUNET_break_op (0);
    174         return GNUNET_SYSERR;
    175       }
    176       if (0 == strcmp (type,
    177                        "undeposited"))
    178       {
    179         /* An "undeposited" entry (MerchantAbortPayRefundUndepositedStatus)
    180            carries no exchange_status and no refund confirmation to verify. */
    181         ac->type = TALER_MERCHANT_POAC_UNDEPOSITED;
    182         continue;
    183       }
    184       if (0 == strcmp (type,
    185                        "success"))
    186       {
    187         struct GNUNET_JSON_Specification spec_detail[] = {
    188           GNUNET_JSON_spec_uint32 ("exchange_status",
    189                                    &ac->details.success.exchange_status),
    190           GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    191                                        &ac->details.success.exchange_sig),
    192           GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    193                                        &ac->details.success.exchange_pub),
    194           GNUNET_JSON_spec_end ()
    195         };
    196 
    197         ac->type = TALER_MERCHANT_POAC_SUCCESS;
    198         if (GNUNET_OK !=
    199             GNUNET_JSON_parse (refund,
    200                                spec_detail,
    201                                NULL, NULL))
    202         {
    203           GNUNET_break_op (0);
    204           return GNUNET_SYSERR;
    205         }
    206         if (MHD_HTTP_OK != ac->details.success.exchange_status)
    207         {
    208           GNUNET_break_op (0);
    209           return GNUNET_SYSERR;
    210         }
    211 
    212         if (GNUNET_OK !=
    213             TALER_exchange_online_refund_confirmation_verify (
    214               &poah->h_contract,
    215               &poah->coins[i].coin_pub,
    216               &poah->merchant_pub,
    217               0,   /* transaction id */
    218               &poah->coins[i].amount_with_fee,
    219               &ac->details.success.exchange_pub,
    220               &ac->details.success.exchange_sig))
    221         {
    222           GNUNET_break_op (0);
    223           return GNUNET_SYSERR;
    224         }
    225         continue;
    226       }
    227       if (0 == strcmp (type,
    228                        "failure"))
    229       {
    230         struct GNUNET_JSON_Specification spec_detail[] = {
    231           GNUNET_JSON_spec_uint32 ("exchange_status",
    232                                    &ac->details.failure.exchange_status),
    233           TALER_JSON_spec_ec ("exchange_code",
    234                               &ac->details.failure.exchange_code),
    235           GNUNET_JSON_spec_mark_optional (
    236             GNUNET_JSON_spec_object_const ("exchange_reply",
    237                                            &ac->details.failure.exchange_reply),
    238             NULL),
    239           GNUNET_JSON_spec_end ()
    240         };
    241 
    242         ac->type = TALER_MERCHANT_POAC_FAILURE;
    243         if (GNUNET_OK !=
    244             GNUNET_JSON_parse (refund,
    245                                spec_detail,
    246                                NULL, NULL))
    247         {
    248           GNUNET_break_op (0);
    249           return GNUNET_SYSERR;
    250         }
    251         continue;
    252       }
    253       GNUNET_break_op (0);
    254       return GNUNET_SYSERR;
    255     }
    256     switch (ar->hr.http_status)
    257     {
    258     case MHD_HTTP_OK:
    259       ar->details.ok.num_aborts = num_refunds;
    260       ar->details.ok.aborts = res;
    261       break;
    262     case MHD_HTTP_BAD_GATEWAY:
    263       ar->details.bad_gateway.num_aborts = num_refunds;
    264       ar->details.bad_gateway.aborts = res;
    265       break;
    266     default:
    267       GNUNET_assert (0);
    268     }
    269     poah->cb (poah->cb_cls,
    270               ar);
    271     poah->cb = NULL;
    272   }
    273   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 handle_abort_finished (void *cls,
    287                        long response_code,
    288                        const void *response)
    289 {
    290   struct TALER_MERCHANT_PostOrdersAbortHandle *poah = cls;
    291   const json_t *json = response;
    292   struct TALER_MERCHANT_PostOrdersAbortResponse ar = {
    293     .hr.http_status = (unsigned int) response_code,
    294     .hr.reply = json
    295   };
    296 
    297   poah->job = NULL;
    298   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    299               "POST /orders/$ID/abort completed with response code %u\n",
    300               (unsigned int) response_code);
    301   switch (response_code)
    302   {
    303   case 0:
    304     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    305     break;
    306   case MHD_HTTP_OK:
    307     if (GNUNET_OK ==
    308         check_abort_refund (poah,
    309                             &ar,
    310                             json))
    311     {
    312       TALER_MERCHANT_post_orders_abort_cancel (poah);
    313       return;
    314     }
    315     ar.hr.http_status = 0;
    316     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    317     break;
    318   case MHD_HTTP_BAD_REQUEST:
    319     ar.hr.ec = TALER_JSON_get_error_code (json);
    320     ar.hr.hint = TALER_JSON_get_error_hint (json);
    321     break;
    322   case MHD_HTTP_FORBIDDEN:
    323     ar.hr.ec = TALER_JSON_get_error_code (json);
    324     ar.hr.hint = TALER_JSON_get_error_hint (json);
    325     break;
    326   case MHD_HTTP_NOT_FOUND:
    327     ar.hr.ec = TALER_JSON_get_error_code (json);
    328     ar.hr.hint = TALER_JSON_get_error_hint (json);
    329     break;
    330   case MHD_HTTP_REQUEST_TIMEOUT:
    331     ar.hr.ec = TALER_JSON_get_error_code (json);
    332     ar.hr.hint = TALER_JSON_get_error_hint (json);
    333     break;
    334   case MHD_HTTP_PRECONDITION_FAILED:
    335     ar.hr.ec = TALER_JSON_get_error_code (json);
    336     ar.hr.hint = TALER_JSON_get_error_hint (json);
    337     break;
    338   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    339     ar.hr.ec = TALER_JSON_get_error_code (json);
    340     ar.hr.hint = TALER_JSON_get_error_hint (json);
    341     break;
    342   case MHD_HTTP_BAD_GATEWAY:
    343     if (GNUNET_OK ==
    344         check_abort_refund (poah,
    345                             &ar,
    346                             json))
    347     {
    348       TALER_MERCHANT_post_orders_abort_cancel (poah);
    349       return;
    350     }
    351     ar.hr.http_status = 0;
    352     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    353     break;
    354   case MHD_HTTP_GATEWAY_TIMEOUT:
    355     TALER_MERCHANT_parse_error_details_ (json,
    356                                          response_code,
    357                                          &ar.hr);
    358     ar.details.gateway_timeout.exchange_ec = ar.hr.exchange_code;
    359     ar.details.gateway_timeout.exchange_http_status
    360       = ar.hr.exchange_http_status;
    361     break;
    362   default:
    363     TALER_MERCHANT_parse_error_details_ (json,
    364                                          response_code,
    365                                          &ar.hr);
    366     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    367                 "Unexpected response code %u/%d\n",
    368                 (unsigned int) response_code,
    369                 (int) ar.hr.ec);
    370     GNUNET_break_op (0);
    371     break;
    372   }
    373   poah->cb (poah->cb_cls,
    374             &ar);
    375   TALER_MERCHANT_post_orders_abort_cancel (poah);
    376 }
    377 
    378 
    379 struct TALER_MERCHANT_PostOrdersAbortHandle *
    380 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 {
    389   struct TALER_MERCHANT_PostOrdersAbortHandle *poah;
    390 
    391   if (MAX_REFUNDS < num_coins)
    392   {
    393     GNUNET_break (0);
    394     return NULL;
    395   }
    396   poah = GNUNET_new (struct TALER_MERCHANT_PostOrdersAbortHandle);
    397   poah->ctx = ctx;
    398   poah->base_url = GNUNET_strdup (url);
    399   poah->order_id = GNUNET_strdup (order_id);
    400   poah->merchant_pub = *merchant_pub;
    401   poah->h_contract = *h_contract;
    402   poah->num_coins = num_coins;
    403   poah->coins = GNUNET_new_array (num_coins,
    404                                   struct TALER_MERCHANT_PostOrdersAbortCoin);
    405   GNUNET_memcpy (
    406     poah->coins,
    407     coins,
    408     num_coins * sizeof (struct TALER_MERCHANT_PostOrdersAbortCoin));
    409   return poah;
    410 }
    411 
    412 
    413 enum TALER_ErrorCode
    414 TALER_MERCHANT_post_orders_abort_start (
    415   struct TALER_MERCHANT_PostOrdersAbortHandle *poah,
    416   TALER_MERCHANT_PostOrdersAbortCallback cb,
    417   TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls)
    418 {
    419   json_t *abort_obj;
    420   json_t *j_coins;
    421   CURL *eh;
    422 
    423   poah->cb = cb;
    424   poah->cb_cls = cb_cls;
    425   {
    426     char *path;
    427 
    428     GNUNET_asprintf (&path,
    429                      "orders/%s/abort",
    430                      poah->order_id);
    431     poah->url = TALER_url_join (poah->base_url,
    432                                 path,
    433                                 NULL);
    434     GNUNET_free (path);
    435   }
    436   if (NULL == poah->url)
    437     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    438   j_coins = json_array ();
    439   if (NULL == j_coins)
    440   {
    441     GNUNET_break (0);
    442     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    443   }
    444   for (unsigned int i = 0; i<poah->num_coins; i++)
    445   {
    446     const struct TALER_MERCHANT_PostOrdersAbortCoin *ac = &poah->coins[i];
    447     json_t *j_coin;
    448 
    449     j_coin = GNUNET_JSON_PACK (
    450       GNUNET_JSON_pack_data_auto ("coin_pub",
    451                                   &ac->coin_pub),
    452       GNUNET_JSON_pack_string ("exchange_url",
    453                                ac->exchange_url));
    454     if (0 !=
    455         json_array_append_new (j_coins,
    456                                j_coin))
    457     {
    458       GNUNET_break (0);
    459       json_decref (j_coins);
    460       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    461     }
    462   }
    463   abort_obj = GNUNET_JSON_PACK (
    464     GNUNET_JSON_pack_array_steal ("coins",
    465                                   j_coins),
    466     GNUNET_JSON_pack_data_auto ("h_contract",
    467                                 &poah->h_contract));
    468   eh = TALER_MERCHANT_curl_easy_get_ (poah->url);
    469   if ( (NULL == eh) ||
    470        (GNUNET_OK !=
    471         TALER_curl_easy_post (&poah->post_ctx,
    472                               eh,
    473                               abort_obj)) )
    474   {
    475     GNUNET_break (0);
    476     json_decref (abort_obj);
    477     if (NULL != eh)
    478       curl_easy_cleanup (eh);
    479     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    480   }
    481   json_decref (abort_obj);
    482   poah->job = GNUNET_CURL_job_add2 (poah->ctx,
    483                                     eh,
    484                                     poah->post_ctx.headers,
    485                                     &handle_abort_finished,
    486                                     poah);
    487   if (NULL == poah->job)
    488     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    489   return TALER_EC_NONE;
    490 }
    491 
    492 
    493 void
    494 TALER_MERCHANT_post_orders_abort_cancel (
    495   struct TALER_MERCHANT_PostOrdersAbortHandle *poah)
    496 {
    497   if (NULL != poah->job)
    498   {
    499     GNUNET_CURL_job_cancel (poah->job);
    500     poah->job = NULL;
    501   }
    502   TALER_curl_easy_post_finished (&poah->post_ctx);
    503   GNUNET_free (poah->coins);
    504   GNUNET_free (poah->order_id);
    505   GNUNET_free (poah->url);
    506   GNUNET_free (poah->base_url);
    507   GNUNET_free (poah);
    508 }
    509 
    510 
    511 /* end of merchant_api_post-orders-ORDER_ID-abort-new.c */