merchant

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

merchant_api_post-orders-ORDER_ID-refund.c (11052B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-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-refund-new.c
     21  * @brief Implementation of the POST /orders/$ORDER_ID/refund 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-refund.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/refund operation.
     46  */
     47 struct TALER_MERCHANT_PostOrdersRefundHandle
     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_PostOrdersRefundCallback cb;
     68 
     69   /**
     70    * Closure for @a cb.
     71    */
     72   TALER_MERCHANT_POST_ORDERS_REFUND_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    * Hash of the contract terms.
     91    */
     92   struct TALER_PrivateContractHashP h_contract_terms;
     93 };
     94 
     95 
     96 /**
     97  * Function called when we're done processing the
     98  * HTTP POST /orders/$ORDER_ID/refund request.
     99  *
    100  * @param cls the `struct TALER_MERCHANT_PostOrdersRefundHandle`
    101  * @param response_code HTTP response code, 0 on error
    102  * @param response response body, NULL if not in JSON
    103  */
    104 static void
    105 handle_refund_finished (void *cls,
    106                         long response_code,
    107                         const void *response)
    108 {
    109   struct TALER_MERCHANT_PostOrdersRefundHandle *porh = cls;
    110   const json_t *json = response;
    111   struct TALER_MERCHANT_PostOrdersRefundResponse orr = {
    112     .hr.http_status = (unsigned int) response_code,
    113     .hr.reply = json
    114   };
    115 
    116   porh->job = NULL;
    117   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    118               "POST /orders/$ID/refund completed with response code %u\n",
    119               (unsigned int) response_code);
    120   switch (response_code)
    121   {
    122   case 0:
    123     orr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    124     break;
    125   case MHD_HTTP_OK:
    126     {
    127       const json_t *refunds;
    128       unsigned int refund_len;
    129       struct GNUNET_JSON_Specification spec[] = {
    130         GNUNET_JSON_spec_array_const (
    131           "refunds",
    132           &refunds),
    133         TALER_JSON_spec_amount_any (
    134           "refund_amount",
    135           &orr.details.ok.refund_amount),
    136         GNUNET_JSON_spec_fixed_auto (
    137           "merchant_pub",
    138           &orr.details.ok.merchant_pub),
    139         GNUNET_JSON_spec_end ()
    140       };
    141 
    142       if (GNUNET_OK !=
    143           GNUNET_JSON_parse (json,
    144                              spec,
    145                              NULL, NULL))
    146       {
    147         GNUNET_break_op (0);
    148         orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    149         orr.hr.http_status = 0;
    150         break;
    151       }
    152       refund_len = (unsigned int) json_array_size (refunds);
    153       if ( (json_array_size (refunds) != (size_t) refund_len) ||
    154            (refund_len > MAX_REFUNDS) )
    155       {
    156         GNUNET_break (0);
    157         orr.hr.ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
    158         orr.hr.http_status = 0;
    159         break;
    160       }
    161       {
    162         struct TALER_MERCHANT_PostOrdersRefundDetail rds[GNUNET_NZL (
    163                                                            refund_len)];
    164 
    165         memset (rds,
    166                 0,
    167                 sizeof (rds));
    168         for (unsigned int i = 0; i < refund_len; i++)
    169         {
    170           struct TALER_MERCHANT_PostOrdersRefundDetail *rd = &rds[i];
    171           const json_t *jrefund = json_array_get (refunds,
    172                                                   i);
    173           const char *refund_status_type;
    174           uint32_t exchange_status;
    175           uint32_t eec = 0;
    176           struct GNUNET_JSON_Specification espec[] = {
    177             GNUNET_JSON_spec_string ("type",
    178                                      &refund_status_type),
    179             GNUNET_JSON_spec_uint32 ("exchange_status",
    180                                      &exchange_status),
    181             GNUNET_JSON_spec_uint64 ("rtransaction_id",
    182                                      &rd->rtransaction_id),
    183             GNUNET_JSON_spec_fixed_auto ("coin_pub",
    184                                          &rd->coin_pub),
    185             TALER_JSON_spec_amount_any ("refund_amount",
    186                                         &rd->refund_amount),
    187             GNUNET_JSON_spec_mark_optional (
    188               GNUNET_JSON_spec_object_const ("exchange_reply",
    189                                              &rd->exchange_reply),
    190               NULL),
    191             GNUNET_JSON_spec_mark_optional (
    192               GNUNET_JSON_spec_uint32 ("exchange_code",
    193                                        &eec),
    194               NULL),
    195             GNUNET_JSON_spec_end ()
    196           };
    197 
    198           if (GNUNET_OK !=
    199               GNUNET_JSON_parse (jrefund,
    200                                  espec,
    201                                  NULL, NULL))
    202           {
    203             GNUNET_break_op (0);
    204             orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    205             orr.hr.http_status = 0;
    206             goto finish;
    207           }
    208 
    209           rd->exchange_http_status = exchange_status;
    210           rd->ec = (enum TALER_ErrorCode) eec;
    211           switch (exchange_status)
    212           {
    213           case MHD_HTTP_OK:
    214             {
    215               struct GNUNET_JSON_Specification rspec[] = {
    216                 GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    217                                              &rd->exchange_sig),
    218                 GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    219                                              &rd->exchange_pub),
    220                 GNUNET_JSON_spec_end ()
    221               };
    222 
    223               if (GNUNET_OK !=
    224                   GNUNET_JSON_parse (jrefund,
    225                                      rspec,
    226                                      NULL,
    227                                      NULL))
    228               {
    229                 GNUNET_break_op (0);
    230                 orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    231                 orr.hr.http_status = 0;
    232                 goto finish;
    233               }
    234               if (0 != strcmp ("success",
    235                                refund_status_type))
    236               {
    237                 GNUNET_break_op (0);
    238                 orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    239                 orr.hr.http_status = 0;
    240                 goto finish;
    241               }
    242             }
    243             break;
    244           default:
    245             if (0 != strcmp ("failure",
    246                              refund_status_type))
    247             {
    248               GNUNET_break_op (0);
    249               orr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    250               orr.hr.http_status = 0;
    251               goto finish;
    252             }
    253           }
    254         }
    255 
    256         orr.details.ok.refunds = rds;
    257         orr.details.ok.num_refunds = refund_len;
    258         porh->cb (porh->cb_cls,
    259                   &orr);
    260         TALER_MERCHANT_post_orders_refund_cancel (porh);
    261         return;
    262       }
    263     }
    264     break;
    265   case MHD_HTTP_NO_CONTENT:
    266     break;
    267   case MHD_HTTP_NOT_FOUND:
    268     orr.hr.ec = TALER_JSON_get_error_code (json);
    269     orr.hr.hint = TALER_JSON_get_error_hint (json);
    270     break;
    271   default:
    272     GNUNET_break_op (0);
    273     TALER_MERCHANT_parse_error_details_ (json,
    274                                          response_code,
    275                                          &orr.hr);
    276     break;
    277   }
    278 finish:
    279   porh->cb (porh->cb_cls,
    280             &orr);
    281   TALER_MERCHANT_post_orders_refund_cancel (porh);
    282 }
    283 
    284 
    285 struct TALER_MERCHANT_PostOrdersRefundHandle *
    286 TALER_MERCHANT_post_orders_refund_create (
    287   struct GNUNET_CURL_Context *ctx,
    288   const char *url,
    289   const char *order_id,
    290   const struct TALER_PrivateContractHashP *h_contract_terms)
    291 {
    292   struct TALER_MERCHANT_PostOrdersRefundHandle *porh;
    293 
    294   porh = GNUNET_new (struct TALER_MERCHANT_PostOrdersRefundHandle);
    295   porh->ctx = ctx;
    296   porh->base_url = GNUNET_strdup (url);
    297   porh->order_id = GNUNET_strdup (order_id);
    298   porh->h_contract_terms = *h_contract_terms;
    299   return porh;
    300 }
    301 
    302 
    303 enum TALER_ErrorCode
    304 TALER_MERCHANT_post_orders_refund_start (
    305   struct TALER_MERCHANT_PostOrdersRefundHandle *porh,
    306   TALER_MERCHANT_PostOrdersRefundCallback cb,
    307   TALER_MERCHANT_POST_ORDERS_REFUND_RESULT_CLOSURE *cb_cls)
    308 {
    309   json_t *req_obj;
    310   CURL *eh;
    311 
    312   porh->cb = cb;
    313   porh->cb_cls = cb_cls;
    314   {
    315     char *path;
    316 
    317     GNUNET_asprintf (&path,
    318                      "orders/%s/refund",
    319                      porh->order_id);
    320     porh->url = TALER_url_join (porh->base_url,
    321                                 path,
    322                                 NULL);
    323     GNUNET_free (path);
    324   }
    325   if (NULL == porh->url)
    326     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    327   req_obj = GNUNET_JSON_PACK (
    328     GNUNET_JSON_pack_data_auto ("h_contract",
    329                                 &porh->h_contract_terms));
    330   eh = TALER_MERCHANT_curl_easy_get_ (porh->url);
    331   if ( (NULL == eh) ||
    332        (GNUNET_OK !=
    333         TALER_curl_easy_post (&porh->post_ctx,
    334                               eh,
    335                               req_obj)) )
    336   {
    337     GNUNET_break (0);
    338     json_decref (req_obj);
    339     if (NULL != eh)
    340       curl_easy_cleanup (eh);
    341     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    342   }
    343   json_decref (req_obj);
    344   porh->job = GNUNET_CURL_job_add2 (porh->ctx,
    345                                     eh,
    346                                     porh->post_ctx.headers,
    347                                     &handle_refund_finished,
    348                                     porh);
    349   if (NULL == porh->job)
    350     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    351   return TALER_EC_NONE;
    352 }
    353 
    354 
    355 void
    356 TALER_MERCHANT_post_orders_refund_cancel (
    357   struct TALER_MERCHANT_PostOrdersRefundHandle *porh)
    358 {
    359   if (NULL != porh->job)
    360   {
    361     GNUNET_CURL_job_cancel (porh->job);
    362     porh->job = NULL;
    363   }
    364   TALER_curl_easy_post_finished (&porh->post_ctx);
    365   GNUNET_free (porh->order_id);
    366   GNUNET_free (porh->url);
    367   GNUNET_free (porh->base_url);
    368   GNUNET_free (porh);
    369 }
    370 
    371 
    372 /* end of merchant_api_post-orders-ORDER_ID-refund-new.c */