merchant

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

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


      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     ar->details.ok.num_aborts = num_refunds;
    207     ar->details.ok.aborts = res;
    208     poah->cb (poah->cb_cls,
    209               ar);
    210     poah->cb = NULL;
    211   }
    212   return GNUNET_OK;
    213 }
    214 
    215 
    216 /**
    217  * Function called when we're done processing the
    218  * HTTP POST /orders/$ID/abort request.
    219  *
    220  * @param cls the `struct TALER_MERCHANT_PostOrdersAbortHandle`
    221  * @param response_code HTTP response code, 0 on error
    222  * @param response response body, NULL if not in JSON
    223  */
    224 static void
    225 handle_abort_finished (void *cls,
    226                        long response_code,
    227                        const void *response)
    228 {
    229   struct TALER_MERCHANT_PostOrdersAbortHandle *poah = cls;
    230   const json_t *json = response;
    231   struct TALER_MERCHANT_PostOrdersAbortResponse ar = {
    232     .hr.http_status = (unsigned int) response_code,
    233     .hr.reply = json
    234   };
    235 
    236   poah->job = NULL;
    237   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    238               "POST /orders/$ID/abort completed with response code %u\n",
    239               (unsigned int) response_code);
    240   switch (response_code)
    241   {
    242   case 0:
    243     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    244     break;
    245   case MHD_HTTP_OK:
    246     if (GNUNET_OK ==
    247         check_abort_refund (poah,
    248                             &ar,
    249                             json))
    250     {
    251       TALER_MERCHANT_post_orders_abort_cancel (poah);
    252       return;
    253     }
    254     ar.hr.http_status = 0;
    255     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    256     break;
    257   case MHD_HTTP_BAD_REQUEST:
    258     ar.hr.ec = TALER_JSON_get_error_code (json);
    259     ar.hr.hint = TALER_JSON_get_error_hint (json);
    260     break;
    261   case MHD_HTTP_FORBIDDEN:
    262     ar.hr.ec = TALER_JSON_get_error_code (json);
    263     ar.hr.hint = TALER_JSON_get_error_hint (json);
    264     break;
    265   case MHD_HTTP_NOT_FOUND:
    266     ar.hr.ec = TALER_JSON_get_error_code (json);
    267     ar.hr.hint = TALER_JSON_get_error_hint (json);
    268     break;
    269   case MHD_HTTP_REQUEST_TIMEOUT:
    270     ar.hr.ec = TALER_JSON_get_error_code (json);
    271     ar.hr.hint = TALER_JSON_get_error_hint (json);
    272     break;
    273   case MHD_HTTP_PRECONDITION_FAILED:
    274     ar.hr.ec = TALER_JSON_get_error_code (json);
    275     ar.hr.hint = TALER_JSON_get_error_hint (json);
    276     break;
    277   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    278     ar.hr.ec = TALER_JSON_get_error_code (json);
    279     ar.hr.hint = TALER_JSON_get_error_hint (json);
    280     break;
    281   case MHD_HTTP_BAD_GATEWAY:
    282     TALER_MERCHANT_parse_error_details_ (json,
    283                                          response_code,
    284                                          &ar.hr);
    285     break;
    286   case MHD_HTTP_GATEWAY_TIMEOUT:
    287     ar.hr.ec = TALER_JSON_get_error_code (json);
    288     ar.hr.hint = TALER_JSON_get_error_hint (json);
    289     break;
    290   default:
    291     TALER_MERCHANT_parse_error_details_ (json,
    292                                          response_code,
    293                                          &ar.hr);
    294     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    295                 "Unexpected response code %u/%d\n",
    296                 (unsigned int) response_code,
    297                 (int) ar.hr.ec);
    298     GNUNET_break_op (0);
    299     break;
    300   }
    301   poah->cb (poah->cb_cls,
    302             &ar);
    303   TALER_MERCHANT_post_orders_abort_cancel (poah);
    304 }
    305 
    306 
    307 struct TALER_MERCHANT_PostOrdersAbortHandle *
    308 TALER_MERCHANT_post_orders_abort_create (
    309   struct GNUNET_CURL_Context *ctx,
    310   const char *url,
    311   const char *order_id,
    312   const struct TALER_MerchantPublicKeyP *merchant_pub,
    313   const struct TALER_PrivateContractHashP *h_contract,
    314   unsigned int num_coins,
    315   const struct TALER_MERCHANT_PostOrdersAbortCoin coins[static num_coins])
    316 {
    317   struct TALER_MERCHANT_PostOrdersAbortHandle *poah;
    318 
    319   poah = GNUNET_new (struct TALER_MERCHANT_PostOrdersAbortHandle);
    320   poah->ctx = ctx;
    321   poah->base_url = GNUNET_strdup (url);
    322   poah->order_id = GNUNET_strdup (order_id);
    323   poah->merchant_pub = *merchant_pub;
    324   poah->h_contract = *h_contract;
    325   poah->num_coins = num_coins;
    326   poah->coins = GNUNET_new_array (num_coins,
    327                                   struct TALER_MERCHANT_PostOrdersAbortCoin);
    328   GNUNET_memcpy (poah->coins,
    329                  coins,
    330                  num_coins * sizeof (struct TALER_MERCHANT_PostOrdersAbortCoin))
    331   ;
    332   return poah;
    333 }
    334 
    335 
    336 enum TALER_ErrorCode
    337 TALER_MERCHANT_post_orders_abort_start (
    338   struct TALER_MERCHANT_PostOrdersAbortHandle *poah,
    339   TALER_MERCHANT_PostOrdersAbortCallback cb,
    340   TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls)
    341 {
    342   json_t *abort_obj;
    343   json_t *j_coins;
    344   CURL *eh;
    345 
    346   poah->cb = cb;
    347   poah->cb_cls = cb_cls;
    348   {
    349     char *path;
    350 
    351     GNUNET_asprintf (&path,
    352                      "orders/%s/abort",
    353                      poah->order_id);
    354     poah->url = TALER_url_join (poah->base_url,
    355                                 path,
    356                                 NULL);
    357     GNUNET_free (path);
    358   }
    359   if (NULL == poah->url)
    360     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    361   j_coins = json_array ();
    362   if (NULL == j_coins)
    363   {
    364     GNUNET_break (0);
    365     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    366   }
    367   for (unsigned int i = 0; i<poah->num_coins; i++)
    368   {
    369     const struct TALER_MERCHANT_PostOrdersAbortCoin *ac = &poah->coins[i];
    370     json_t *j_coin;
    371 
    372     j_coin = GNUNET_JSON_PACK (
    373       GNUNET_JSON_pack_data_auto ("coin_pub",
    374                                   &ac->coin_pub),
    375       TALER_JSON_pack_amount ("contribution",
    376                               &ac->amount_with_fee),
    377       GNUNET_JSON_pack_string ("exchange_url",
    378                                ac->exchange_url));
    379     if (0 !=
    380         json_array_append_new (j_coins,
    381                                j_coin))
    382     {
    383       GNUNET_break (0);
    384       json_decref (j_coins);
    385       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    386     }
    387   }
    388   abort_obj = GNUNET_JSON_PACK (
    389     GNUNET_JSON_pack_array_steal ("coins",
    390                                   j_coins),
    391     GNUNET_JSON_pack_data_auto ("h_contract",
    392                                 &poah->h_contract));
    393   eh = TALER_MERCHANT_curl_easy_get_ (poah->url);
    394   if ( (NULL == eh) ||
    395        (GNUNET_OK !=
    396         TALER_curl_easy_post (&poah->post_ctx,
    397                               eh,
    398                               abort_obj)) )
    399   {
    400     GNUNET_break (0);
    401     json_decref (abort_obj);
    402     if (NULL != eh)
    403       curl_easy_cleanup (eh);
    404     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    405   }
    406   json_decref (abort_obj);
    407   poah->job = GNUNET_CURL_job_add2 (poah->ctx,
    408                                     eh,
    409                                     poah->post_ctx.headers,
    410                                     &handle_abort_finished,
    411                                     poah);
    412   if (NULL == poah->job)
    413     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    414   return TALER_EC_NONE;
    415 }
    416 
    417 
    418 void
    419 TALER_MERCHANT_post_orders_abort_cancel (
    420   struct TALER_MERCHANT_PostOrdersAbortHandle *poah)
    421 {
    422   if (NULL != poah->job)
    423   {
    424     GNUNET_CURL_job_cancel (poah->job);
    425     poah->job = NULL;
    426   }
    427   TALER_curl_easy_post_finished (&poah->post_ctx);
    428   GNUNET_free (poah->coins);
    429   GNUNET_free (poah->order_id);
    430   GNUNET_free (poah->url);
    431   GNUNET_free (poah->base_url);
    432   GNUNET_free (poah);
    433 }
    434 
    435 
    436 /* end of merchant_api_post-orders-ORDER_ID-abort-new.c */