merchant_api_patch-private-webhooks-WEBHOOK_ID.c (9499B)
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 merchant_api_patch-private-webhooks-WEBHOOK_ID.c 21 * @brief Implementation of the PATCH /private/webhooks/$WEBHOOK_ID 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/patch-private-webhooks-WEBHOOK_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 PATCH /private/webhooks/$WEBHOOK_ID operation. 39 */ 40 struct TALER_MERCHANT_PatchPrivateWebhookHandle 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_PatchPrivateWebhookCallback cb; 61 62 /** 63 * Closure for @a cb. 64 */ 65 TALER_MERCHANT_PATCH_PRIVATE_WEBHOOK_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 * Identifier of the webhook to update. 79 */ 80 char *webhook_id; 81 82 /** 83 * New event type. 84 */ 85 char *event_type; 86 87 /** 88 * New notification URL template. 89 */ 90 char *url_template; 91 92 /** 93 * New HTTP method. 94 */ 95 char *http_method; 96 97 /** 98 * Optional new header template. 99 */ 100 char *header_template; 101 102 /** 103 * Optional new body template. 104 */ 105 char *body_template; 106 }; 107 108 109 /** 110 * Function called when we're done processing the 111 * HTTP PATCH /private/webhooks/$WEBHOOK_ID request. 112 * 113 * @param cls the `struct TALER_MERCHANT_PatchPrivateWebhookHandle` 114 * @param response_code HTTP response code, 0 on error 115 * @param response response body, NULL if not in JSON 116 */ 117 static void 118 handle_patch_webhook_finished (void *cls, 119 long response_code, 120 const void *response) 121 { 122 struct TALER_MERCHANT_PatchPrivateWebhookHandle *wph = cls; 123 const json_t *json = response; 124 struct TALER_MERCHANT_PatchPrivateWebhookResponse wpr = { 125 .hr.http_status = (unsigned int) response_code, 126 .hr.reply = json 127 }; 128 129 wph->job = NULL; 130 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 131 "PATCH /private/webhooks/$WEBHOOK_ID completed with response code %u\n", 132 (unsigned int) response_code); 133 switch (response_code) 134 { 135 case 0: 136 wpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 137 break; 138 case MHD_HTTP_NO_CONTENT: 139 break; 140 case MHD_HTTP_BAD_REQUEST: 141 wpr.hr.ec = TALER_JSON_get_error_code (json); 142 wpr.hr.hint = TALER_JSON_get_error_hint (json); 143 GNUNET_break_op (0); 144 /* This should never happen, either us 145 * or the merchant is buggy (or API version conflict); 146 * just pass JSON reply to the application */ 147 break; 148 case MHD_HTTP_UNAUTHORIZED: 149 wpr.hr.ec = TALER_JSON_get_error_code (json); 150 wpr.hr.hint = TALER_JSON_get_error_hint (json); 151 /* Nothing really to verify, merchant says we need to authenticate. */ 152 break; 153 case MHD_HTTP_FORBIDDEN: 154 wpr.hr.ec = TALER_JSON_get_error_code (json); 155 wpr.hr.hint = TALER_JSON_get_error_hint (json); 156 break; 157 case MHD_HTTP_NOT_FOUND: 158 wpr.hr.ec = TALER_JSON_get_error_code (json); 159 wpr.hr.hint = TALER_JSON_get_error_hint (json); 160 break; 161 case MHD_HTTP_CONFLICT: 162 wpr.hr.ec = TALER_JSON_get_error_code (json); 163 wpr.hr.hint = TALER_JSON_get_error_hint (json); 164 break; 165 case MHD_HTTP_INTERNAL_SERVER_ERROR: 166 wpr.hr.ec = TALER_JSON_get_error_code (json); 167 wpr.hr.hint = TALER_JSON_get_error_hint (json); 168 /* Server had an internal issue; we should retry, 169 but this API leaves this to the application */ 170 break; 171 default: 172 TALER_MERCHANT_parse_error_details_ (json, 173 response_code, 174 &wpr.hr); 175 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 176 "Unexpected response code %u/%d\n", 177 (unsigned int) response_code, 178 (int) wpr.hr.ec); 179 GNUNET_break_op (0); 180 break; 181 } 182 wph->cb (wph->cb_cls, 183 &wpr); 184 TALER_MERCHANT_patch_private_webhook_cancel (wph); 185 } 186 187 188 struct TALER_MERCHANT_PatchPrivateWebhookHandle * 189 TALER_MERCHANT_patch_private_webhook_create ( 190 struct GNUNET_CURL_Context *ctx, 191 const char *url, 192 const char *webhook_id, 193 const char *event_type, 194 const char *url_template, 195 const char *http_method) 196 { 197 struct TALER_MERCHANT_PatchPrivateWebhookHandle *wph; 198 199 wph = GNUNET_new (struct TALER_MERCHANT_PatchPrivateWebhookHandle); 200 wph->ctx = ctx; 201 wph->base_url = GNUNET_strdup (url); 202 wph->webhook_id = GNUNET_strdup (webhook_id); 203 wph->event_type = GNUNET_strdup (event_type); 204 wph->url_template = GNUNET_strdup (url_template); 205 wph->http_method = GNUNET_strdup (http_method); 206 return wph; 207 } 208 209 210 enum GNUNET_GenericReturnValue 211 TALER_MERCHANT_patch_private_webhook_set_options_ ( 212 struct TALER_MERCHANT_PatchPrivateWebhookHandle *wph, 213 unsigned int num_options, 214 const struct TALER_MERCHANT_PatchPrivateWebhookOptionValue *options) 215 { 216 for (unsigned int i = 0; i < num_options; i++) 217 { 218 switch (options[i].option) 219 { 220 case TALER_MERCHANT_PATCH_PRIVATE_WEBHOOK_OPTION_END: 221 return GNUNET_OK; 222 case TALER_MERCHANT_PATCH_PRIVATE_WEBHOOK_OPTION_HEADER_TEMPLATE: 223 GNUNET_free (wph->header_template); 224 wph->header_template = GNUNET_strdup (options[i].details.header_template); 225 break; 226 case TALER_MERCHANT_PATCH_PRIVATE_WEBHOOK_OPTION_BODY_TEMPLATE: 227 GNUNET_free (wph->body_template); 228 wph->body_template = GNUNET_strdup (options[i].details.body_template); 229 break; 230 default: 231 GNUNET_break (0); 232 return GNUNET_SYSERR; 233 } 234 } 235 return GNUNET_OK; 236 } 237 238 239 enum TALER_ErrorCode 240 TALER_MERCHANT_patch_private_webhook_start ( 241 struct TALER_MERCHANT_PatchPrivateWebhookHandle *wph, 242 TALER_MERCHANT_PatchPrivateWebhookCallback cb, 243 TALER_MERCHANT_PATCH_PRIVATE_WEBHOOK_RESULT_CLOSURE *cb_cls) 244 { 245 json_t *req_obj; 246 CURL *eh; 247 248 wph->cb = cb; 249 wph->cb_cls = cb_cls; 250 { 251 char *path; 252 253 GNUNET_asprintf (&path, 254 "private/webhooks/%s", 255 wph->webhook_id); 256 wph->url = TALER_url_join (wph->base_url, 257 path, 258 NULL); 259 GNUNET_free (path); 260 } 261 if (NULL == wph->url) 262 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 263 req_obj = GNUNET_JSON_PACK ( 264 GNUNET_JSON_pack_string ("event_type", 265 wph->event_type), 266 GNUNET_JSON_pack_string ("url", 267 wph->url_template), 268 GNUNET_JSON_pack_string ("http_method", 269 wph->http_method), 270 GNUNET_JSON_pack_allow_null ( 271 GNUNET_JSON_pack_string ("header_template", 272 wph->header_template)), 273 GNUNET_JSON_pack_allow_null ( 274 GNUNET_JSON_pack_string ("body_template", 275 wph->body_template))); 276 eh = TALER_MERCHANT_curl_easy_get_ (wph->url); 277 if ( (NULL == eh) || 278 (GNUNET_OK != 279 TALER_curl_easy_post (&wph->post_ctx, 280 eh, 281 req_obj)) ) 282 { 283 GNUNET_break (0); 284 json_decref (req_obj); 285 if (NULL != eh) 286 curl_easy_cleanup (eh); 287 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 288 } 289 json_decref (req_obj); 290 GNUNET_assert (CURLE_OK == 291 curl_easy_setopt (eh, 292 CURLOPT_CUSTOMREQUEST, 293 MHD_HTTP_METHOD_PATCH)); 294 wph->job = GNUNET_CURL_job_add2 (wph->ctx, 295 eh, 296 wph->post_ctx.headers, 297 &handle_patch_webhook_finished, 298 wph); 299 if (NULL == wph->job) 300 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 301 return TALER_EC_NONE; 302 } 303 304 305 void 306 TALER_MERCHANT_patch_private_webhook_cancel ( 307 struct TALER_MERCHANT_PatchPrivateWebhookHandle *wph) 308 { 309 if (NULL != wph->job) 310 { 311 GNUNET_CURL_job_cancel (wph->job); 312 wph->job = NULL; 313 } 314 TALER_curl_easy_post_finished (&wph->post_ctx); 315 GNUNET_free (wph->url); 316 GNUNET_free (wph->header_template); 317 GNUNET_free (wph->body_template); 318 GNUNET_free (wph->base_url); 319 GNUNET_free (wph->webhook_id); 320 GNUNET_free (wph->event_type); 321 GNUNET_free (wph->url_template); 322 GNUNET_free (wph->http_method); 323 GNUNET_free (wph); 324 } 325 326 327 /* end of merchant_api_patch-private-webhooks-WEBHOOK_ID.c */