merchant

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

merchant_api_post_order_abort.c (12531B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2023 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_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 "merchant_api_curl_defaults.h"
     34 #include "merchant_api_common.h"
     35 #include <taler/taler_json_lib.h>
     36 #include <taler/taler_signatures.h>
     37 #include <taler/taler_exchange_service.h>
     38 #include <taler/taler_curl_lib.h>
     39 
     40 
     41 /**
     42  * Maximum number of refunds we return.
     43  */
     44 #define MAX_REFUNDS 1024
     45 
     46 
     47 /**
     48  * @brief An abort Handle
     49  */
     50 struct TALER_MERCHANT_OrderAbortHandle
     51 {
     52   /**
     53    * Hash of the contract.
     54    */
     55   struct TALER_PrivateContractHashP h_contract_terms;
     56 
     57   /**
     58    * Public key of the merchant.
     59    */
     60   struct TALER_MerchantPublicKeyP merchant_pub;
     61 
     62   /**
     63    * The url for this request.
     64    */
     65   char *url;
     66 
     67   /**
     68    * Handle for the request.
     69    */
     70   struct GNUNET_CURL_Job *job;
     71 
     72   /**
     73    * Function to call with the result.
     74    */
     75   TALER_MERCHANT_AbortCallback abort_cb;
     76 
     77   /**
     78    * Closure for @a abort_cb.
     79    */
     80   void *abort_cb_cls;
     81 
     82   /**
     83    * Reference to the execution context.
     84    */
     85   struct GNUNET_CURL_Context *ctx;
     86 
     87   /**
     88    * Minor context that holds body and headers.
     89    */
     90   struct TALER_CURL_PostContext post_ctx;
     91 
     92   /**
     93    * The coins we are aborting on.
     94    */
     95   struct TALER_MERCHANT_AbortCoin *coins;
     96 
     97   /**
     98    * Number of @e coins we are paying with.
     99    */
    100   unsigned int num_coins;
    101 
    102 };
    103 
    104 
    105 /**
    106  * Check that the response for an abort is well-formed,
    107  * and call the application callback with the result if it is
    108  * OK. Otherwise returns #GNUNET_SYSERR.
    109  *
    110  * @param oah handle to operation that created the reply
    111  * @param[in] ar abort response, partially initialized
    112  * @param json the reply to parse
    113  * @return #GNUNET_OK on success
    114  */
    115 static enum GNUNET_GenericReturnValue
    116 check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah,
    117                     struct TALER_MERCHANT_AbortResponse *ar,
    118                     const json_t *json)
    119 {
    120   const json_t *refunds;
    121   unsigned int num_refunds;
    122   struct GNUNET_JSON_Specification spec[] = {
    123     GNUNET_JSON_spec_array_const ("refunds",
    124                                   &refunds),
    125     GNUNET_JSON_spec_end ()
    126   };
    127 
    128   if (GNUNET_OK !=
    129       GNUNET_JSON_parse (json,
    130                          spec,
    131                          NULL, NULL))
    132   {
    133     GNUNET_break_op (0);
    134     return GNUNET_SYSERR;
    135   }
    136   num_refunds = (unsigned int) json_array_size (refunds);
    137   if ( (json_array_size (refunds) != (size_t)  num_refunds) ||
    138        (num_refunds > MAX_REFUNDS) )
    139   {
    140     GNUNET_break (0);
    141     return GNUNET_SYSERR;
    142   }
    143 
    144   {
    145     struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)];
    146 
    147     for (unsigned int i = 0; i<num_refunds; i++)
    148     {
    149       json_t *refund = json_array_get (refunds, i);
    150       uint32_t exchange_status;
    151       struct GNUNET_JSON_Specification spec_es[] = {
    152         GNUNET_JSON_spec_uint32 ("exchange_status",
    153                                  &exchange_status),
    154         GNUNET_JSON_spec_end ()
    155       };
    156 
    157       if (GNUNET_OK !=
    158           GNUNET_JSON_parse (refund,
    159                              spec_es,
    160                              NULL, NULL))
    161       {
    162         GNUNET_break_op (0);
    163         return GNUNET_SYSERR;
    164       }
    165       if (MHD_HTTP_OK == exchange_status)
    166       {
    167         struct GNUNET_JSON_Specification spec_detail[] = {
    168           GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    169                                        &res[i].exchange_sig),
    170           GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    171                                        &res[i].exchange_pub),
    172           GNUNET_JSON_spec_end ()
    173         };
    174 
    175         if (GNUNET_OK !=
    176             GNUNET_JSON_parse (refund,
    177                                spec_detail,
    178                                NULL, NULL))
    179         {
    180           GNUNET_break_op (0);
    181           return GNUNET_SYSERR;
    182         }
    183 
    184         if (GNUNET_OK !=
    185             TALER_exchange_online_refund_confirmation_verify (
    186               &oah->h_contract_terms,
    187               &oah->coins[i].coin_pub,
    188               &oah->merchant_pub,
    189               0,                                                   /* transaction id */
    190               &oah->coins[i].amount_with_fee,
    191               &res[i].exchange_pub,
    192               &res[i].exchange_sig))
    193         {
    194           GNUNET_break_op (0);
    195           return GNUNET_SYSERR;
    196         }
    197       }
    198     }
    199     ar->details.ok.merchant_pub = &oah->merchant_pub;
    200     ar->details.ok.num_aborts = num_refunds;
    201     ar->details.ok.aborts = res;
    202     oah->abort_cb (oah->abort_cb_cls,
    203                    ar);
    204     oah->abort_cb = NULL;
    205   }
    206   return GNUNET_OK;
    207 }
    208 
    209 
    210 /**
    211  * Function called when we're done processing the
    212  * abort request.
    213  *
    214  * @param cls the `struct TALER_MERCHANT_OrderAbortHandle`
    215  * @param response_code HTTP response code, 0 on error
    216  * @param response response body, NULL if not in JSON
    217  */
    218 static void
    219 handle_abort_finished (void *cls,
    220                        long response_code,
    221                        const void *response)
    222 {
    223   struct TALER_MERCHANT_OrderAbortHandle *oah = cls;
    224   const json_t *json = response;
    225   struct TALER_MERCHANT_AbortResponse ar = {
    226     .hr.http_status = (unsigned int) response_code,
    227     .hr.reply = json
    228   };
    229 
    230   oah->job = NULL;
    231   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    232               "/pay completed with response code %u\n",
    233               (unsigned int) response_code);
    234   switch (response_code)
    235   {
    236   case 0:
    237     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    238     break;
    239   case MHD_HTTP_OK:
    240     if (GNUNET_OK ==
    241         check_abort_refund (oah,
    242                             &ar,
    243                             json))
    244     {
    245       TALER_MERCHANT_order_abort_cancel (oah);
    246       return;
    247     }
    248     ar.hr.http_status = 0;
    249     ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    250     break;
    251   case MHD_HTTP_BAD_REQUEST:
    252     ar.hr.ec = TALER_JSON_get_error_code (json);
    253     ar.hr.hint = TALER_JSON_get_error_hint (json);
    254     /* This should never happen, either us or the
    255        merchant is buggy (or API version conflict); just
    256        pass JSON reply to the application */
    257     break;
    258   case MHD_HTTP_FORBIDDEN:
    259     ar.hr.ec = TALER_JSON_get_error_code (json);
    260     ar.hr.hint = TALER_JSON_get_error_hint (json);
    261     break;
    262   case MHD_HTTP_NOT_FOUND:
    263     ar.hr.ec = TALER_JSON_get_error_code (json);
    264     ar.hr.hint = TALER_JSON_get_error_hint (json);
    265     /* Nothing really to verify, this should never
    266  happen, we should pass the JSON reply to the
    267        application */
    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     /* Nothing really to verify, merchant says one of
    273        the signatures is invalid; as we checked them,
    274        this should never happen, we should pass the JSON
    275        reply to the application */
    276     break;
    277   case MHD_HTTP_PRECONDITION_FAILED:
    278     /* Our *payment* already succeeded fully. */
    279     ar.hr.ec = TALER_JSON_get_error_code (json);
    280     ar.hr.hint = TALER_JSON_get_error_hint (json);
    281     break;
    282   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    283     ar.hr.ec = TALER_JSON_get_error_code (json);
    284     ar.hr.hint = TALER_JSON_get_error_hint (json);
    285     /* Server had an internal issue; we should retry,
    286        but this API leaves this to the application */
    287     break;
    288   case MHD_HTTP_BAD_GATEWAY:
    289     TALER_MERCHANT_parse_error_details_ (json,
    290                                          response_code,
    291                                          &ar.hr);
    292     /* Nothing really to verify, the merchant is blaming the exchange.
    293        We should pass the JSON reply to the application */
    294     break;
    295   default:
    296     /* unexpected response code */
    297     TALER_MERCHANT_parse_error_details_ (json,
    298                                          response_code,
    299                                          &ar.hr);
    300     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    301                 "Unexpected response code %u/%d\n",
    302                 (unsigned int) response_code,
    303                 (int) ar.hr.ec);
    304     GNUNET_break_op (0);
    305     break;
    306   }
    307   oah->abort_cb (oah->abort_cb_cls,
    308                  &ar);
    309   TALER_MERCHANT_order_abort_cancel (oah);
    310 }
    311 
    312 
    313 struct TALER_MERCHANT_OrderAbortHandle *
    314 TALER_MERCHANT_order_abort (
    315   struct GNUNET_CURL_Context *ctx,
    316   const char *merchant_url,
    317   const char *order_id,
    318   const struct TALER_MerchantPublicKeyP *merchant_pub,
    319   const struct TALER_PrivateContractHashP *h_contract,
    320   unsigned int num_coins,
    321   const struct TALER_MERCHANT_AbortCoin coins[static num_coins],
    322   TALER_MERCHANT_AbortCallback cb,
    323   void *cb_cls)
    324 {
    325   struct TALER_MERCHANT_OrderAbortHandle *oah;
    326   json_t *abort_obj;
    327   json_t *j_coins;
    328 
    329   j_coins = json_array ();
    330   if (NULL == j_coins)
    331   {
    332     GNUNET_break (0);
    333     return NULL;
    334   }
    335   for (unsigned int i = 0; i<num_coins; i++)
    336   {
    337     const struct TALER_MERCHANT_AbortCoin *ac = &coins[i];
    338     json_t *j_coin;
    339 
    340     /* create JSON for this coin */
    341     j_coin = GNUNET_JSON_PACK (
    342       GNUNET_JSON_pack_data_auto ("coin_pub",
    343                                   &ac->coin_pub),
    344       /* FIXME: no longer needed since **v18**, remove eventually! */
    345       TALER_JSON_pack_amount ("contribution",
    346                               &ac->amount_with_fee),
    347       GNUNET_JSON_pack_string ("exchange_url",
    348                                ac->exchange_url));
    349     if (0 !=
    350         json_array_append_new (j_coins,
    351                                j_coin))
    352     {
    353       GNUNET_break (0);
    354       json_decref (j_coins);
    355       return NULL;
    356     }
    357   }
    358   abort_obj = GNUNET_JSON_PACK (
    359     GNUNET_JSON_pack_array_steal ("coins",
    360                                   j_coins),
    361     GNUNET_JSON_pack_data_auto ("h_contract",
    362                                 h_contract));
    363   oah = GNUNET_new (struct TALER_MERCHANT_OrderAbortHandle);
    364   oah->h_contract_terms = *h_contract;
    365   oah->merchant_pub = *merchant_pub;
    366   oah->ctx = ctx;
    367   oah->abort_cb = cb;
    368   oah->abort_cb_cls = cb_cls;
    369   {
    370     char *path;
    371 
    372     GNUNET_asprintf (&path,
    373                      "orders/%s/abort",
    374                      order_id);
    375     oah->url = TALER_url_join (merchant_url,
    376                                path,
    377                                NULL);
    378     GNUNET_free (path);
    379   }
    380   if (NULL == oah->url)
    381   {
    382     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    383                 "Could not construct request URL.\n");
    384     json_decref (abort_obj);
    385     GNUNET_free (oah);
    386     return NULL;
    387   }
    388   oah->num_coins = num_coins;
    389   oah->coins = GNUNET_new_array (num_coins,
    390                                  struct TALER_MERCHANT_AbortCoin);
    391   GNUNET_memcpy (oah->coins,
    392                  coins,
    393                  num_coins * sizeof (struct TALER_MERCHANT_AbortCoin));
    394   {
    395     CURL *eh;
    396 
    397     eh = TALER_MERCHANT_curl_easy_get_ (oah->url);
    398     if (GNUNET_OK !=
    399         TALER_curl_easy_post (&oah->post_ctx,
    400                               eh,
    401                               abort_obj))
    402     {
    403       GNUNET_break (0);
    404       curl_easy_cleanup (eh);
    405       json_decref (abort_obj);
    406       GNUNET_free (oah);
    407       return NULL;
    408     }
    409     json_decref (abort_obj);
    410     oah->job = GNUNET_CURL_job_add2 (ctx,
    411                                      eh,
    412                                      oah->post_ctx.headers,
    413                                      &handle_abort_finished,
    414                                      oah);
    415   }
    416   return oah;
    417 }
    418 
    419 
    420 void
    421 TALER_MERCHANT_order_abort_cancel (
    422   struct TALER_MERCHANT_OrderAbortHandle *oah)
    423 {
    424   if (NULL != oah->job)
    425   {
    426     GNUNET_CURL_job_cancel (oah->job);
    427     oah->job = NULL;
    428   }
    429   TALER_curl_easy_post_finished (&oah->post_ctx);
    430   GNUNET_free (oah->coins);
    431   GNUNET_free (oah->url);
    432   GNUNET_free (oah);
    433 }
    434 
    435 
    436 /* end of merchant_api_post_order_abort.c */