merchant

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

merchant_api_wallet_post_order_refund.c (10359B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Lesser General Public License as published by the Free Software
      7   Foundation; either version 2.1, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
     12 
     13   You should have received a copy of the GNU Lesser General Public License along with
     14   TALER; see the file COPYING.LGPL.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file merchant_api_wallet_post_order_refund.c
     19  * @brief Implementation of the (public) POST /orders/ID/refund request
     20  * @author Jonathan Buchanan
     21  */
     22 #include "platform.h"
     23 #include <curl/curl.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include "taler_merchant_service.h"
     29 #include "merchant_api_common.h"
     30 #include "merchant_api_curl_defaults.h"
     31 #include <taler/taler_json_lib.h>
     32 #include <taler/taler_signatures.h>
     33 #include <taler/taler_curl_lib.h>
     34 
     35 /**
     36  * Maximum number of refunds we return.
     37  */
     38 #define MAX_REFUNDS 1024
     39 
     40 /**
     41  * Handle for a (public) POST /orders/ID/refund operation.
     42  */
     43 struct TALER_MERCHANT_WalletOrderRefundHandle
     44 {
     45   /**
     46    * Complete URL where the backend offers /refund
     47    */
     48   char *url;
     49 
     50   /**
     51    * Minor context that holds body and headers.
     52    */
     53   struct TALER_CURL_PostContext post_ctx;
     54 
     55   /**
     56    * The CURL context to connect to the backend
     57    */
     58   struct GNUNET_CURL_Context *ctx;
     59 
     60   /**
     61    * The callback to pass the backend response to
     62    */
     63   TALER_MERCHANT_WalletRefundCallback cb;
     64 
     65   /**
     66    * Clasure to pass to the callback
     67    */
     68   void *cb_cls;
     69 
     70   /**
     71    * Handle for the request
     72    */
     73   struct GNUNET_CURL_Job *job;
     74 };
     75 
     76 
     77 /**
     78  * Callback to process (public) POST /orders/ID/refund response
     79  *
     80  * @param cls the `struct TALER_MERCHANT_OrderRefundHandle`
     81  * @param response_code HTTP response code, 0 on error
     82  * @param response response body, NULL if not JSON
     83  */
     84 static void
     85 handle_refund_finished (void *cls,
     86                         long response_code,
     87                         const void *response)
     88 {
     89   struct TALER_MERCHANT_WalletOrderRefundHandle *orh = cls;
     90   const json_t *json = response;
     91   struct TALER_MERCHANT_WalletRefundResponse wrr = {
     92     .hr.http_status = (unsigned int) response_code,
     93     .hr.reply = json
     94   };
     95 
     96   orh->job = NULL;
     97   switch (response_code)
     98   {
     99   case 0:
    100     wrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    101     break;
    102   case MHD_HTTP_OK:
    103     {
    104       const json_t *refunds;
    105       unsigned int refund_len;
    106       struct GNUNET_JSON_Specification spec[] = {
    107         TALER_JSON_spec_amount_any (
    108           "refund_amount",
    109           &wrr.details.ok.refund_amount),
    110         GNUNET_JSON_spec_array_const (
    111           "refunds",
    112           &refunds),
    113         GNUNET_JSON_spec_fixed_auto (
    114           "merchant_pub",
    115           &wrr.details.ok.merchant_pub),
    116         GNUNET_JSON_spec_end ()
    117       };
    118 
    119       if (GNUNET_OK !=
    120           GNUNET_JSON_parse (json,
    121                              spec,
    122                              NULL, NULL))
    123       {
    124         GNUNET_break_op (0);
    125         wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    126         wrr.hr.http_status = 0;
    127         break;
    128       }
    129       refund_len = json_array_size (refunds);
    130       if ( (json_array_size (refunds) != (size_t)  refund_len) ||
    131            (refund_len > MAX_REFUNDS) )
    132       {
    133         GNUNET_break (0);
    134         wrr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
    135         wrr.hr.http_status = 0;
    136         break;
    137       }
    138       {
    139         struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (refund_len)];
    140 
    141         memset (rds,
    142                 0,
    143                 sizeof (rds));
    144         for (unsigned int i = 0; i<refund_len; i++)
    145         {
    146           struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
    147           const json_t *jrefund = json_array_get (refunds,
    148                                                   i);
    149           const char *refund_status_type;
    150           uint32_t exchange_status;
    151           uint32_t eec = 0;
    152           struct GNUNET_JSON_Specification espec[] = {
    153             GNUNET_JSON_spec_string ("type",
    154                                      &refund_status_type),
    155             GNUNET_JSON_spec_uint32 ("exchange_status",
    156                                      &exchange_status),
    157             GNUNET_JSON_spec_uint64 ("rtransaction_id",
    158                                      &rd->rtransaction_id),
    159             GNUNET_JSON_spec_fixed_auto ("coin_pub",
    160                                          &rd->coin_pub),
    161             TALER_JSON_spec_amount_any ("refund_amount",
    162                                         &rd->refund_amount),
    163             GNUNET_JSON_spec_mark_optional (
    164               GNUNET_JSON_spec_object_const ("exchange_reply",
    165                                              &rd->hr.reply),
    166               NULL),
    167             GNUNET_JSON_spec_mark_optional (
    168               GNUNET_JSON_spec_uint32 ("exchange_code",
    169                                        &eec),
    170               NULL),
    171             GNUNET_JSON_spec_end ()
    172           };
    173 
    174           if (GNUNET_OK !=
    175               GNUNET_JSON_parse (jrefund,
    176                                  espec,
    177                                  NULL, NULL))
    178           {
    179             GNUNET_break_op (0);
    180             wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    181             wrr.hr.http_status = 0;
    182             goto finish;
    183           }
    184 
    185           rd->hr.http_status = exchange_status;
    186           rd->hr.ec = (enum TALER_ErrorCode) eec;
    187           switch (exchange_status)
    188           {
    189           case MHD_HTTP_OK:
    190             {
    191               struct GNUNET_JSON_Specification rspec[] = {
    192                 GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    193                                              &rd->details.ok.exchange_sig),
    194                 GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    195                                              &rd->details.ok.exchange_pub),
    196                 GNUNET_JSON_spec_end ()
    197               };
    198 
    199               if (GNUNET_OK !=
    200                   GNUNET_JSON_parse (jrefund,
    201                                      rspec,
    202                                      NULL,
    203                                      NULL))
    204               {
    205                 GNUNET_break_op (0);
    206                 wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    207                 wrr.hr.http_status = 0;
    208                 goto finish;
    209               }
    210               /* check that type field is correct */
    211               if (0 != strcmp ("success",
    212                                refund_status_type))
    213               {
    214                 GNUNET_break_op (0);
    215                 wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    216                 wrr.hr.http_status = 0;
    217                 goto finish;
    218               }
    219             }
    220             break; /* end MHD_HTTP_OK */
    221           default:
    222             if (0 != strcmp ("failure",
    223                              refund_status_type))
    224             {
    225               GNUNET_break_op (0);
    226               wrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    227               wrr.hr.http_status = 0;
    228               goto finish;
    229             }
    230           } /* switch on exchange status code */
    231         } /* for all refunds */
    232 
    233         wrr.details.ok.refunds = rds;
    234         wrr.details.ok.refunds_length = refund_len;
    235         orh->cb (orh->cb_cls,
    236                  &wrr);
    237         TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
    238         return;
    239       } /* end 'rds' scope */
    240     } /* case MHD_HTTP_OK */
    241     break;
    242   case MHD_HTTP_NO_CONTENT:
    243     break;
    244   case MHD_HTTP_CONFLICT:
    245   case MHD_HTTP_NOT_FOUND:
    246     wrr.hr.ec = TALER_JSON_get_error_code (json);
    247     wrr.hr.hint = TALER_JSON_get_error_hint (json);
    248     break;
    249   default:
    250     GNUNET_break_op (0); /* unexpected status code */
    251     TALER_MERCHANT_parse_error_details_ (json,
    252                                          response_code,
    253                                          &wrr.hr);
    254     break;
    255   }
    256 finish:
    257   orh->cb (orh->cb_cls,
    258            &wrr);
    259   TALER_MERCHANT_wallet_post_order_refund_cancel (orh);
    260 }
    261 
    262 
    263 struct TALER_MERCHANT_WalletOrderRefundHandle *
    264 TALER_MERCHANT_wallet_post_order_refund (
    265   struct GNUNET_CURL_Context *ctx,
    266   const char *backend_url,
    267   const char *order_id,
    268   const struct TALER_PrivateContractHashP *h_contract_terms,
    269   TALER_MERCHANT_WalletRefundCallback cb,
    270   void *cb_cls)
    271 {
    272   struct TALER_MERCHANT_WalletOrderRefundHandle *orh;
    273   json_t *req;
    274   CURL *eh;
    275 
    276   orh = GNUNET_new (struct TALER_MERCHANT_WalletOrderRefundHandle);
    277   orh->ctx = ctx;
    278   orh->cb = cb;
    279   orh->cb_cls = cb_cls;
    280   {
    281     char *path;
    282 
    283     GNUNET_asprintf (&path,
    284                      "orders/%s/refund",
    285                      order_id);
    286     orh->url = TALER_url_join (backend_url,
    287                                path,
    288                                NULL);
    289     GNUNET_free (path);
    290   }
    291   if (NULL == orh->url)
    292   {
    293     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    294                 "Could not construct request URL.\n");
    295     GNUNET_free (orh);
    296     return NULL;
    297   }
    298   req = GNUNET_JSON_PACK (
    299     GNUNET_JSON_pack_data_auto ("h_contract",
    300                                 h_contract_terms));
    301   eh = TALER_MERCHANT_curl_easy_get_ (orh->url);
    302   if (GNUNET_OK !=
    303       TALER_curl_easy_post (&orh->post_ctx,
    304                             eh,
    305                             req))
    306   {
    307     GNUNET_break (0);
    308     json_decref (req);
    309     curl_easy_cleanup (eh);
    310     GNUNET_free (orh->url);
    311     GNUNET_free (orh);
    312     return NULL;
    313   }
    314   json_decref (req);
    315   orh->job = GNUNET_CURL_job_add2 (ctx,
    316                                    eh,
    317                                    orh->post_ctx.headers,
    318                                    &handle_refund_finished,
    319                                    orh);
    320   if (NULL == orh->job)
    321   {
    322     GNUNET_free (orh->url);
    323     GNUNET_free (orh);
    324     return NULL;
    325   }
    326   return orh;
    327 }
    328 
    329 
    330 void
    331 TALER_MERCHANT_wallet_post_order_refund_cancel (
    332   struct TALER_MERCHANT_WalletOrderRefundHandle *orh)
    333 {
    334   if (NULL != orh->job)
    335   {
    336     GNUNET_CURL_job_cancel (orh->job);
    337     orh->job = NULL;
    338   }
    339   TALER_curl_easy_post_finished (&orh->post_ctx);
    340   GNUNET_free (orh->url);
    341   GNUNET_free (orh);
    342 }
    343 
    344 
    345 /* end of merchant_api_wallet_post_order_refund.c */