paivana-httpd_pay.c (11709B)
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. 72 */ 73 struct TALER_MERCHANT_GetPrivateOrderHandle *co; 74 75 /** 76 * Response to return. 77 */ 78 struct MHD_Response *response; 79 80 /** 81 * ID of the order the client claims to have paid. 82 */ 83 const char *order_id; 84 85 /** 86 * Website the order is supposed to have paid for. 87 */ 88 const char *website; 89 90 /** 91 * Client-side nonce. 92 */ 93 struct PAIVANA_Nonce nonce; 94 95 /** 96 * 97 */ 98 struct GNUNET_TIME_Timestamp cur_time; 99 100 /** 101 * HTTP status to return in combination with @e resp to the client. 102 */ 103 unsigned int response_status; 104 105 }; 106 107 108 /** 109 * Head of DLL of suspended requests. 110 */ 111 static struct PayRequest *ph_head; 112 113 /** 114 * Tail of DLL of suspended requests. 115 */ 116 static struct PayRequest *ph_tail; 117 118 119 void 120 PAIVANA_HTTPD_payment_shutdown () 121 { 122 while (NULL != ph_head) 123 { 124 struct PayRequest *ph = ph_head; 125 126 GNUNET_CONTAINER_DLL_remove (ph_head, 127 ph_tail, 128 ph); 129 MHD_resume_connection (ph->connection); 130 } 131 } 132 133 134 struct PayRequest * 135 PAIVANA_HTTPD_payment_create (struct MHD_Connection *connection) 136 { 137 struct PayRequest *ph; 138 139 ph = GNUNET_new (struct PayRequest); 140 ph->connection = connection; 141 return ph; 142 } 143 144 145 /** 146 * Check that the @a contract that was paid is reasonable for the 147 * request in @a ph, that is that we would indeed consider this 148 * contract to apply for the website and duration indicated 149 * in @a ph. If it does not apply, a response must be set in 150 * @a ph. 151 * 152 * @param[in,out] ph request to check 153 * @param contract contract to check 154 * @return true if the contract is good for the request, 155 * false if not and thus a response object was created in @a ph 156 */ 157 static bool 158 check_contract (struct PayRequest *ph, 159 const json_t *contract) 160 { 161 struct GNUNET_TIME_Timestamp max_time 162 = GNUNET_TIME_UNIT_FOREVER_TS; 163 const char *target = NULL; 164 struct GNUNET_JSON_Specification spec[] = { 165 GNUNET_JSON_spec_mark_optional ( 166 GNUNET_JSON_spec_string ("fulfillment_url", 167 &target), 168 NULL), 169 GNUNET_JSON_spec_mark_optional ( 170 GNUNET_JSON_spec_timestamp ("max_pickup_time", 171 &max_time), 172 NULL), 173 GNUNET_JSON_spec_end () 174 }; 175 enum GNUNET_GenericReturnValue ret; 176 const char *ename; 177 unsigned int eline; 178 179 ret = GNUNET_JSON_parse (contract, 180 spec, 181 &ename, 182 &eline); 183 if (GNUNET_OK != ret) 184 { 185 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 186 "Encountered contract with unexpected fields: %s@%u\n", 187 ename, 188 eline); 189 return true; 190 } 191 if ( (NULL != target) && 192 (0 != strcmp (target, 193 ph->website)) ) 194 { 195 GNUNET_break_op (0); 196 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_WRONG_ORDER, 197 ph->order_id); 198 ph->response_status = MHD_HTTP_CONFLICT; 199 return false; 200 } 201 if (GNUNET_TIME_timestamp_cmp (ph->cur_time, 202 >, 203 max_time)) 204 { 205 GNUNET_break_op (0); 206 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_TOO_LATE, 207 ph->order_id); 208 ph->response_status = MHD_HTTP_GONE; 209 return false; 210 } 211 return true; 212 } 213 214 215 /** 216 * Handle response from the GET /private/orders/$ORDER_ID request. 217 * 218 * @param ph the payment request we are processing 219 * @param osr response details 220 */ 221 static void 222 order_status_cb (struct PayRequest *ph, 223 const struct TALER_MERCHANT_GetPrivateOrderResponse *osr) 224 { 225 ph->co = NULL; 226 switch (osr->hr.http_status) 227 { 228 case MHD_HTTP_OK: 229 if (TALER_MERCHANT_OSC_PAID != osr->details.ok.status) 230 { 231 GNUNET_break_op (0); 232 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_PAYMENT_MISSING, 233 ph->order_id); 234 ph->response_status = MHD_HTTP_BAD_REQUEST; 235 } 236 else 237 { 238 void *ca; 239 size_t ca_len; 240 char *cookie; 241 struct MHD_Response *resp; 242 243 if (! check_contract (ph, 244 osr->details.ok.details.paid.contract_terms)) 245 return; 246 GNUNET_break (PAIVANA_HTTPD_get_client_address (ph->connection, 247 &ca, 248 &ca_len)); 249 cookie = PAIVANA_HTTPD_compute_cookie (ph->cur_time, 250 ph->website, 251 ca_len, 252 ca); 253 GNUNET_free (ca); 254 resp = MHD_create_response_from_buffer (0, 255 NULL, 256 MHD_RESPMEM_PERSISTENT); 257 GNUNET_assert (MHD_YES == 258 MHD_add_response_header (resp, 259 MHD_HTTP_HEADER_SET_COOKIE, 260 cookie)); 261 GNUNET_assert (MHD_YES == 262 MHD_add_response_header (resp, 263 MHD_HTTP_HEADER_LOCATION, 264 ph->website)); 265 GNUNET_free (cookie); 266 TALER_MHD_add_global_headers (resp, 267 false); 268 ph->response = resp; 269 ph->response_status = MHD_HTTP_SEE_OTHER; 270 } 271 break; 272 case MHD_HTTP_FORBIDDEN: 273 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_BACKEND_REFUSED, 274 NULL); 275 ph->response_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 276 break; 277 case MHD_HTTP_NOT_FOUND: 278 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_ORDER_UNKNOWN, 279 ph->order_id); 280 ph->response_status = MHD_HTTP_NOT_FOUND; 281 break; 282 default: 283 { 284 char code[20]; 285 286 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 287 "Unexpected status code %u from backend\n", 288 osr->hr.http_status); 289 GNUNET_snprintf (code, 290 sizeof (code), 291 "%u", 292 osr->hr.http_status); 293 ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_BACKEND_ERROR, 294 code); 295 ph->response_status = MHD_HTTP_BAD_GATEWAY; 296 } 297 break; 298 } 299 GNUNET_CONTAINER_DLL_insert (ph_head, 300 ph_tail, 301 ph); 302 MHD_resume_connection (ph->connection); 303 TALER_MHD_daemon_trigger (); 304 305 } 306 307 308 enum MHD_Result 309 PAIVANA_HTTPD_payment_handle (struct PayRequest *ph, 310 const char *upload_data, 311 size_t *upload_data_size) 312 { 313 if (NULL == ph->body) 314 { 315 enum GNUNET_GenericReturnValue ret; 316 317 ret = TALER_MHD_parse_post_json (ph->connection, 318 &ph->buffer, 319 upload_data, 320 upload_data_size, 321 &ph->body); 322 if (GNUNET_OK != ret) 323 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 324 if (NULL == ph->body) 325 return MHD_YES; 326 } 327 if (NULL != ph->response) 328 { 329 return MHD_queue_response (ph->connection, 330 ph->response_status, 331 ph->response); 332 } 333 if (NULL == ph->order_id) 334 { 335 struct GNUNET_JSON_Specification spec[] = { 336 GNUNET_JSON_spec_string ("order_id", 337 &ph->order_id), 338 GNUNET_JSON_spec_string ("website", 339 &ph->website), 340 GNUNET_JSON_spec_timestamp ("cur_time", 341 &ph->cur_time), 342 GNUNET_JSON_spec_fixed_auto ("nonce", 343 &ph->nonce), 344 GNUNET_JSON_spec_end () 345 }; 346 enum GNUNET_GenericReturnValue ret; 347 348 ret = TALER_MHD_parse_json_data (ph->connection, 349 ph->body, 350 spec); 351 if (GNUNET_YES != ret) 352 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 353 } 354 GNUNET_assert (NULL == ph->co); 355 ph->co = TALER_MERCHANT_get_private_order_create (PH_ctx, 356 PH_merchant_base_url, 357 ph->order_id); 358 if (NULL == ph->co) 359 { 360 GNUNET_break (0); 361 return TALER_MHD_reply_with_error (ph->connection, 362 MHD_HTTP_INTERNAL_SERVER_ERROR, 363 TALER_EC_PAIVANA_GET_ORDER_FAILED, 364 ph->order_id); 365 } 366 { 367 char *paivana_id; 368 369 paivana_id = PAIVANA_HTTPD_compute_paivana_id (ph->cur_time, 370 ph->website, 371 &ph->nonce); 372 GNUNET_assert ( 373 GNUNET_OK == 374 TALER_MERCHANT_get_private_order_set_options ( 375 ph->co, 376 TALER_MERCHANT_get_private_order_option_session_id ( 377 paivana_id))); 378 GNUNET_free (paivana_id); 379 } 380 GNUNET_CONTAINER_DLL_insert (ph_head, 381 ph_tail, 382 ph); 383 MHD_suspend_connection (ph->connection); 384 GNUNET_assert (TALER_EC_NONE == 385 TALER_MERCHANT_get_private_order_start (ph->co, 386 &order_status_cb, 387 ph)); 388 return MHD_YES; 389 } 390 391 392 void 393 PAIVANA_HTTPD_payment_destroy (struct PayRequest *ph) 394 { 395 TALER_MHD_parse_post_cleanup_callback (ph->buffer); 396 if (NULL != ph->co) 397 TALER_MERCHANT_get_private_order_cancel (ph->co); 398 if (NULL != ph->response) 399 MHD_destroy_response (ph->response); 400 json_decref (ph->body); 401 GNUNET_free (ph); 402 }