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 */