merchant

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

merchant_api_post-templates-TEMPLATE_ID.c (9675B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-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-templates-TEMPLATE_ID.c
     21  * @brief Implementation of the POST /templates/$TEMPLATE_ID 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-templates-TEMPLATE_ID.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 
     36 
     37 /**
     38  * Handle for a POST /templates/$TEMPLATE_ID operation.
     39  */
     40 struct TALER_MERCHANT_PostTemplatesHandle
     41 {
     42   /**
     43    * Base URL of the merchant backend.
     44    */
     45   char *base_url;
     46 
     47   /**
     48    * The full URL for this request.
     49    */
     50   char *url;
     51 
     52   /**
     53    * Handle for the request.
     54    */
     55   struct GNUNET_CURL_Job *job;
     56 
     57   /**
     58    * Function to call with the result.
     59    */
     60   TALER_MERCHANT_PostTemplatesCallback cb;
     61 
     62   /**
     63    * Closure for @a cb.
     64    */
     65   TALER_MERCHANT_POST_TEMPLATES_RESULT_CLOSURE *cb_cls;
     66 
     67   /**
     68    * Reference to the execution context.
     69    */
     70   struct GNUNET_CURL_Context *ctx;
     71 
     72   /**
     73    * Minor context that holds body and headers.
     74    */
     75   struct TALER_CURL_PostContext post_ctx;
     76 
     77   /**
     78    * Template identifier.
     79    */
     80   char *template_id;
     81 
     82   /**
     83    * Optional summary string.
     84    */
     85   const char *summary;
     86 
     87   /**
     88    * Optional amount.
     89    */
     90   const struct TALER_Amount *amount;
     91 
     92   /**
     93    * Optional detailed contract customization (JSON).
     94    */
     95   const json_t *details;
     96 
     97   /**
     98    * Challenge to sign once the order is paid, if any.
     99    */
    100   const struct TALER_PosChallengeP *challenge;
    101 };
    102 
    103 
    104 /**
    105  * Function called when we're done processing the
    106  * HTTP POST /templates/$TEMPLATE_ID request.
    107  *
    108  * @param cls the `struct TALER_MERCHANT_PostTemplatesHandle`
    109  * @param response_code HTTP response code, 0 on error
    110  * @param response response body, NULL if not in JSON
    111  */
    112 static void
    113 handle_post_templates_finished (void *cls,
    114                                 long response_code,
    115                                 const void *response)
    116 {
    117   struct TALER_MERCHANT_PostTemplatesHandle *pth = cls;
    118   const json_t *json = response;
    119   struct TALER_MERCHANT_PostTemplatesResponse ptr = {
    120     .hr.http_status = (unsigned int) response_code,
    121     .hr.reply = json
    122   };
    123   struct TALER_ClaimTokenP token;
    124 
    125   pth->job = NULL;
    126   switch (response_code)
    127   {
    128   case 0:
    129     ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    130     break;
    131   case MHD_HTTP_OK:
    132     {
    133       bool no_token;
    134       bool no_pay_deadline;
    135       struct GNUNET_JSON_Specification spec[] = {
    136         TALER_JSON_spec_slug ("order_id",
    137                               &ptr.details.ok.order_id),
    138         GNUNET_JSON_spec_mark_optional (
    139           GNUNET_JSON_spec_fixed_auto ("token",
    140                                        &token),
    141           &no_token),
    142         GNUNET_JSON_spec_mark_optional (
    143           GNUNET_JSON_spec_timestamp ("pay_deadline",
    144                                       &ptr.details.ok.pay_deadline),
    145           &no_pay_deadline),
    146         GNUNET_JSON_spec_end ()
    147       };
    148 
    149       if (GNUNET_OK !=
    150           GNUNET_JSON_parse (json,
    151                              spec,
    152                              NULL, NULL))
    153       {
    154         GNUNET_break_op (0);
    155         ptr.hr.http_status = 0;
    156         ptr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    157         break;
    158       }
    159       if (! no_token)
    160         ptr.details.ok.token = &token;
    161       if (no_pay_deadline)
    162         ptr.details.ok.pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
    163       break;
    164     }
    165   case MHD_HTTP_BAD_REQUEST:
    166     ptr.hr.ec = TALER_JSON_get_error_code (json);
    167     ptr.hr.hint = TALER_JSON_get_error_hint (json);
    168     break;
    169   case MHD_HTTP_UNAUTHORIZED:
    170     ptr.hr.ec = TALER_JSON_get_error_code (json);
    171     ptr.hr.hint = TALER_JSON_get_error_hint (json);
    172     break;
    173   case MHD_HTTP_FORBIDDEN:
    174     ptr.hr.ec = TALER_JSON_get_error_code (json);
    175     ptr.hr.hint = TALER_JSON_get_error_hint (json);
    176     break;
    177   case MHD_HTTP_NOT_FOUND:
    178     ptr.hr.ec = TALER_JSON_get_error_code (json);
    179     ptr.hr.hint = TALER_JSON_get_error_hint (json);
    180     break;
    181   case MHD_HTTP_CONFLICT:
    182     ptr.hr.ec = TALER_JSON_get_error_code (json);
    183     ptr.hr.hint = TALER_JSON_get_error_hint (json);
    184     break;
    185   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    186     ptr.hr.ec = TALER_JSON_get_error_code (json);
    187     ptr.hr.hint = TALER_JSON_get_error_hint (json);
    188     break;
    189   default:
    190     ptr.hr.ec = TALER_JSON_get_error_code (json);
    191     ptr.hr.hint = TALER_JSON_get_error_hint (json);
    192     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    193                 "Unexpected response code %u/%d\n",
    194                 (unsigned int) response_code,
    195                 (int) ptr.hr.ec);
    196     GNUNET_break_op (0);
    197     break;
    198   }
    199   pth->cb (pth->cb_cls,
    200            &ptr);
    201   TALER_MERCHANT_post_templates_cancel (pth);
    202 }
    203 
    204 
    205 struct TALER_MERCHANT_PostTemplatesHandle *
    206 TALER_MERCHANT_post_templates_create (
    207   struct GNUNET_CURL_Context *ctx,
    208   const char *url,
    209   const char *template_id)
    210 {
    211   struct TALER_MERCHANT_PostTemplatesHandle *pth;
    212 
    213   pth = GNUNET_new (struct TALER_MERCHANT_PostTemplatesHandle);
    214   pth->ctx = ctx;
    215   pth->base_url = GNUNET_strdup (url);
    216   pth->template_id = GNUNET_strdup (template_id);
    217   return pth;
    218 }
    219 
    220 
    221 enum GNUNET_GenericReturnValue
    222 TALER_MERCHANT_post_templates_set_options_ (
    223   struct TALER_MERCHANT_PostTemplatesHandle *pth,
    224   unsigned int num_options,
    225   const struct TALER_MERCHANT_PostTemplatesOptionValue *options)
    226 {
    227   for (unsigned int i = 0; i < num_options; i++)
    228   {
    229     switch (options[i].option)
    230     {
    231     case TALER_MERCHANT_POST_TEMPLATES_OPTION_END:
    232       return GNUNET_OK;
    233     case TALER_MERCHANT_POST_TEMPLATES_OPTION_SUMMARY:
    234       pth->summary = options[i].details.summary;
    235       break;
    236     case TALER_MERCHANT_POST_TEMPLATES_OPTION_AMOUNT:
    237       pth->amount = options[i].details.amount;
    238       break;
    239     case TALER_MERCHANT_POST_TEMPLATES_OPTION_DETAILS:
    240       pth->details = options[i].details.details;
    241       break;
    242     case TALER_MERCHANT_POST_TEMPLATES_OPTION_CHALLENGE:
    243       pth->challenge = options[i].details.challenge;
    244       break;
    245     default:
    246       GNUNET_break (0);
    247       return GNUNET_SYSERR;
    248     }
    249   }
    250   return GNUNET_OK;
    251 }
    252 
    253 
    254 enum TALER_ErrorCode
    255 TALER_MERCHANT_post_templates_start (
    256   struct TALER_MERCHANT_PostTemplatesHandle *pth,
    257   TALER_MERCHANT_PostTemplatesCallback cb,
    258   TALER_MERCHANT_POST_TEMPLATES_RESULT_CLOSURE *cb_cls)
    259 {
    260   json_t *req_obj;
    261   CURL *eh;
    262 
    263   pth->cb = cb;
    264   pth->cb_cls = cb_cls;
    265   {
    266     char *path;
    267 
    268     GNUNET_asprintf (&path,
    269                      "templates/%s",
    270                      pth->template_id);
    271     pth->url = TALER_url_join (pth->base_url,
    272                                path,
    273                                NULL);
    274     GNUNET_free (path);
    275   }
    276   if (NULL == pth->url)
    277     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    278   if (NULL != pth->details)
    279   {
    280     /* The per-request challenge must not change caller-owned details.
    281        Only top-level fields are modified, copy is good. */
    282     req_obj = json_copy ((json_t *) pth->details);
    283     GNUNET_assert (NULL != req_obj);
    284   }
    285   else
    286   {
    287     /* Build fixed-order request with optional summary and amount. */
    288     req_obj = GNUNET_JSON_PACK (
    289       GNUNET_JSON_pack_string ("template_type",
    290                                "fixed-order"),
    291       GNUNET_JSON_pack_allow_null (
    292         GNUNET_JSON_pack_string ("summary",
    293                                  pth->summary)),
    294       GNUNET_JSON_pack_allow_null (
    295         TALER_JSON_pack_amount ("amount",
    296                                 pth->amount)));
    297   }
    298   if (NULL != pth->challenge)
    299   {
    300     GNUNET_assert (0 ==
    301                    json_object_set_new (
    302                      req_obj,
    303                      "challenge",
    304                      GNUNET_JSON_from_data_auto (pth->challenge)));
    305   }
    306   eh = TALER_MERCHANT_curl_easy_get_ (pth->url);
    307   if ( (NULL == eh) ||
    308        (GNUNET_OK !=
    309         TALER_curl_easy_post (&pth->post_ctx,
    310                               eh,
    311                               req_obj)) )
    312   {
    313     GNUNET_break (0);
    314     json_decref (req_obj);
    315     if (NULL != eh)
    316       curl_easy_cleanup (eh);
    317     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    318   }
    319   json_decref (req_obj);
    320   pth->job = GNUNET_CURL_job_add2 (pth->ctx,
    321                                    eh,
    322                                    pth->post_ctx.headers,
    323                                    &handle_post_templates_finished,
    324                                    pth);
    325   if (NULL == pth->job)
    326     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    327   return TALER_EC_NONE;
    328 }
    329 
    330 
    331 void
    332 TALER_MERCHANT_post_templates_cancel (
    333   struct TALER_MERCHANT_PostTemplatesHandle *pth)
    334 {
    335   if (NULL != pth->job)
    336   {
    337     GNUNET_CURL_job_cancel (pth->job);
    338     pth->job = NULL;
    339   }
    340   TALER_curl_easy_post_finished (&pth->post_ctx);
    341   GNUNET_free (pth->template_id);
    342   GNUNET_free (pth->url);
    343   GNUNET_free (pth->base_url);
    344   GNUNET_free (pth);
    345 }
    346 
    347 
    348 /* end of merchant_api_post-templates-TEMPLATE_ID-new.c */