paivana-httpd_pay.c (12685B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2026 Taler Systems SA 4 5 Paivana is free software; you can redistribute it and/or 6 modify it under the terms of the GNU General Public License 7 as published by the Free Software Foundation; either version 8 3, or (at your option) any later version. 9 10 Paivana is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty 12 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 13 the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with Paivana; see the file COPYING. If not, 17 write to the Free Software Foundation, Inc., 51 Franklin 18 Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @author Christian Grothoff 23 * @file paivana-httpd_pay.c 24 * @brief payment processing logic 25 */ 26 #include <microhttpd.h> 27 #include <gnunet/gnunet_util_lib.h> 28 #include <taler/taler_mhd_lib.h> 29 #include <taler/taler_error_codes.h> 30 #include "paivana-httpd_cookie.h" 31 #include "paivana-httpd_helper.h" 32 #include "paivana-httpd_pay.h" 33 34 struct PayRequest; 35 #define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct PayRequest 36 #include "taler/merchant/get-private-orders-ORDER_ID.h" 37 38 39 /** 40 * Handle for processing actual payment. 41 */ 42 struct PayRequest 43 { 44 45 /** 46 * Kept in a DLL while suspended. 47 */ 48 struct PayRequest *next; 49 50 /** 51 * Kept in a DLL while suspended. 52 */ 53 struct PayRequest *prev; 54 55 /** 56 * Connection we are handling. 57 */ 58 struct MHD_Connection *connection; 59 60 /** 61 * Buffer for TALER_MHD_parse_post_json(). 62 */ 63 void *buffer; 64 65 /** 66 * Uploaded JSON body, NULL if none yet. 67 */ 68 json_t *body; 69 70 /** 71 * Handle for our request to the merchant backend. This 72 * struct is in the #ph_head DLL as long as @e co is non-NULL. 73 */ 74 struct TALER_MERCHANT_GetPrivateOrderHandle *co; 75 76 /** 77 * Response to return, NULL if not yet determined. 78 */ 79 struct MHD_Response *response; 80 81 /** 82 * ID of the order the client claims to have paid. Aliased 83 * from @e body. 84 */ 85 const char *order_id; 86 87 /** 88 * Website the order is supposed to have paid for. Aliased 89 * from @e body. 90 */ 91 const char *website; 92 93 /** 94 * Client-side nonce. 95 */ 96 struct PAIVANA_Nonce nonce; 97 98 /** 99 * Expiration time of the cookie. 100 */ 101 struct GNUNET_TIME_Timestamp cur_time; 102 103 /** 104 * HTTP status to return in combination with @e resp to the client. 105 */ 106 unsigned int response_status; 107 108 }; 109 110 111 /** 112 * Head of DLL of suspended requests. 113 */ 114 static struct PayRequest *ph_head; 115 116 /** 117 * Tail of DLL of suspended requests. 118 */ 119 static struct PayRequest *ph_tail; 120 121 122 void 123 PAIVANA_HTTPD_payment_shutdown () 124 { 125 while (NULL != ph_head) 126 { 127 struct PayRequest *ph = ph_head; 128 129 if (NULL != ph->co) 130 { 131 TALER_MERCHANT_get_private_order_cancel (ph->co); 132 ph->co = NULL; 133 } 134 GNUNET_CONTAINER_DLL_remove (ph_head, 135 ph_tail, 136 ph); 137 MHD_resume_connection (ph->connection); 138 /* Note: PAIVANA_HTTPD_payment_destroy() 139 will be called by the owner of 'ph', 140 no need to do it here! */ 141 } 142 } 143 144 145 struct PayRequest * 146 PAIVANA_HTTPD_payment_create (struct MHD_Connection *connection) 147 { 148 struct PayRequest *ph; 149 150 ph = GNUNET_new (struct PayRequest); 151 ph->connection = connection; 152 return ph; 153 } 154 155 156 /** 157 * Check that the @a contract that was paid is reasonable for the 158 * request in @a ph, that is that we would indeed consider this 159 * contract to apply for the website and duration indicated 160 * in @a ph. If it does not apply, a response must be set in 161 * @a ph. 162 * 163 * @param[in,out] ph request to check 164 * @param contract contract to check 165 * @return true if the contract is good for the request, 166 * false if not and thus a response object was created in @a ph 167 */ 168 static bool 169 check_contract (struct PayRequest *ph, 170 const json_t *contract) 171 { 172 struct GNUNET_TIME_Timestamp max_time 173 = GNUNET_TIME_UNIT_FOREVER_TS; 174 const char *target = NULL; 175 struct GNUNET_JSON_Specification spec[] = { 176 GNUNET_JSON_spec_mark_optional ( 177 GNUNET_JSON_spec_string ("fulfillment_url", 178 &target), 179 NULL), 180 GNUNET_JSON_spec_mark_optional ( 181 GNUNET_JSON_spec_timestamp ("max_pickup_time", 182 &max_time), 183 NULL), 184 GNUNET_JSON_spec_end () 185 }; 186 enum GNUNET_GenericReturnValue ret; 187 const char *ename; 188 unsigned int eline; 189 190 ret = GNUNET_JSON_parse (contract, 191 spec, 192 &ename, 193 &eline); 194 if (GNUNET_OK != ret) 195 { 196 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 197 "Encountered contract with unexpected fields: %s@%u\n", 198 ename, 199 eline); 200 /* For now, we tolerate this and just continue. 201 This is a design decision that could be revised. */ 202 return true; 203 } 204 if ( (NULL != target) && 205 (0 != strcmp (target, 206 ph->website)) ) 207 { 208 GNUNET_break_op (0); 209 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_WRONG_ORDER, 210 ph->order_id); 211 ph->response_status = MHD_HTTP_CONFLICT; 212 return false; 213 } 214 if (GNUNET_TIME_timestamp_cmp (ph->cur_time, 215 >, 216 max_time)) 217 { 218 GNUNET_break_op (0); 219 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_TOO_LATE, 220 ph->order_id); 221 ph->response_status = MHD_HTTP_GONE; 222 return false; 223 } 224 return true; 225 } 226 227 228 /** 229 * Handle response from the GET /private/orders/$ORDER_ID request. 230 * 231 * @param ph the payment request we are processing 232 * @param osr response details 233 */ 234 static void 235 order_status_cb (struct PayRequest *ph, 236 const struct TALER_MERCHANT_GetPrivateOrderResponse *osr) 237 { 238 ph->co = NULL; 239 GNUNET_CONTAINER_DLL_remove (ph_head, 240 ph_tail, 241 ph); 242 MHD_resume_connection (ph->connection); 243 TALER_MHD_daemon_trigger (); 244 switch (osr->hr.http_status) 245 { 246 case MHD_HTTP_OK: 247 if (TALER_MERCHANT_OSC_PAID != osr->details.ok.status) 248 { 249 GNUNET_break_op (0); 250 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_PAYMENT_MISSING, 251 ph->order_id); 252 ph->response_status = MHD_HTTP_CONFLICT; 253 } 254 else 255 { 256 void *ca = NULL; 257 size_t ca_len = 0; 258 char *cookie; 259 struct MHD_Response *resp; 260 261 if (! check_contract (ph, 262 osr->details.ok.details.paid.contract_terms)) 263 return; 264 /* If we cannot get the client address, we just 265 use 0/NULL and log an error. */ 266 GNUNET_break (PAIVANA_HTTPD_get_client_address (ph->connection, 267 &ca, 268 &ca_len)); 269 cookie = PAIVANA_HTTPD_compute_cookie (ph->cur_time, 270 ph->website, 271 ca_len, 272 ca); 273 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 274 "Client paid, setting cookie `%s'\n", 275 cookie); 276 GNUNET_free (ca); 277 resp = MHD_create_response_from_buffer (0, 278 NULL, 279 MHD_RESPMEM_PERSISTENT); 280 GNUNET_assert (NULL != resp); 281 GNUNET_assert (MHD_YES == 282 MHD_add_response_header (resp, 283 MHD_HTTP_HEADER_SET_COOKIE, 284 cookie)); 285 GNUNET_assert (MHD_YES == 286 MHD_add_response_header (resp, 287 MHD_HTTP_HEADER_LOCATION, 288 ph->website)); 289 GNUNET_free (cookie); 290 TALER_MHD_add_global_headers (resp, 291 false); 292 ph->response = resp; 293 ph->response_status = MHD_HTTP_SEE_OTHER; 294 } 295 break; 296 case MHD_HTTP_FORBIDDEN: 297 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_BACKEND_REFUSED, 298 NULL); 299 ph->response_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 300 break; 301 case MHD_HTTP_NOT_FOUND: 302 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_ORDER_UNKNOWN, 303 ph->order_id); 304 ph->response_status = MHD_HTTP_NOT_FOUND; 305 break; 306 default: 307 { 308 char code[20]; 309 310 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 311 "Unexpected status code %u from backend\n", 312 osr->hr.http_status); 313 GNUNET_snprintf (code, 314 sizeof (code), 315 "%u", 316 osr->hr.http_status); 317 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_BACKEND_ERROR, 318 code); 319 ph->response_status = MHD_HTTP_BAD_GATEWAY; 320 } 321 break; 322 } 323 } 324 325 326 enum MHD_Result 327 PAIVANA_HTTPD_payment_handle (struct PayRequest *ph, 328 const char *upload_data, 329 size_t *upload_data_size) 330 { 331 if (NULL == ph->body) 332 { 333 enum GNUNET_GenericReturnValue ret; 334 335 ret = TALER_MHD_parse_post_json (ph->connection, 336 &ph->buffer, 337 upload_data, 338 upload_data_size, 339 &ph->body); 340 if (GNUNET_OK != ret) 341 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 342 if (NULL == ph->body) 343 return MHD_YES; 344 } 345 if (NULL != ph->response) 346 { 347 return MHD_queue_response (ph->connection, 348 ph->response_status, 349 ph->response); 350 } 351 if (NULL == ph->order_id) 352 { 353 struct GNUNET_JSON_Specification spec[] = { 354 GNUNET_JSON_spec_string ("order_id", 355 &ph->order_id), 356 GNUNET_JSON_spec_string ("website", 357 &ph->website), 358 GNUNET_JSON_spec_timestamp ("cur_time", 359 &ph->cur_time), 360 GNUNET_JSON_spec_fixed_auto ("nonce", 361 &ph->nonce), 362 GNUNET_JSON_spec_end () 363 }; 364 enum GNUNET_GenericReturnValue ret; 365 366 ret = TALER_MHD_parse_json_data (ph->connection, 367 ph->body, 368 spec); 369 if (GNUNET_YES != ret) 370 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 371 } 372 GNUNET_assert (NULL == ph->co); 373 ph->co = TALER_MERCHANT_get_private_order_create (PH_ctx, 374 PH_merchant_base_url, 375 ph->order_id); 376 if (NULL == ph->co) 377 { 378 GNUNET_break (0); 379 return TALER_MHD_reply_with_error (ph->connection, 380 MHD_HTTP_INTERNAL_SERVER_ERROR, 381 TALER_EC_PAIVANA_GET_ORDER_FAILED, 382 ph->order_id); 383 } 384 { 385 char *paivana_id; 386 387 paivana_id = PAIVANA_HTTPD_compute_paivana_id (ph->cur_time, 388 ph->website, 389 &ph->nonce); 390 GNUNET_assert ( 391 GNUNET_OK == 392 TALER_MERCHANT_get_private_order_set_options ( 393 ph->co, 394 TALER_MERCHANT_get_private_order_option_session_id ( 395 paivana_id))); 396 GNUNET_free (paivana_id); 397 } 398 GNUNET_CONTAINER_DLL_insert (ph_head, 399 ph_tail, 400 ph); 401 MHD_suspend_connection (ph->connection); 402 GNUNET_assert (TALER_EC_NONE == 403 TALER_MERCHANT_get_private_order_start (ph->co, 404 &order_status_cb, 405 ph)); 406 return MHD_YES; 407 } 408 409 410 void 411 PAIVANA_HTTPD_payment_destroy (struct PayRequest *ph) 412 { 413 TALER_MHD_parse_post_cleanup_callback (ph->buffer); 414 if (NULL != ph->co) 415 { 416 TALER_MERCHANT_get_private_order_cancel (ph->co); 417 GNUNET_CONTAINER_DLL_remove (ph_head, 418 ph_tail, 419 ph); 420 ph->co = NULL; 421 } 422 if (NULL != ph->response) 423 { 424 MHD_destroy_response (ph->response); 425 ph->response = NULL; 426 } 427 json_decref (ph->body); 428 GNUNET_free (ph); 429 }