merchant_api_post-private-orders-ORDER_ID-collect.c (7774B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file src/lib/merchant_api_post-private-orders-ORDER_ID-collect.c 19 * @brief Implementation of the POST /private/orders/$ORDER_ID/collect request 20 * @author Bohdan Potuzhnyi 21 * @author Volodymyr Potuzhnyi 22 */ 23 #include "platform.h" 24 #include <curl/curl.h> 25 #include <jansson.h> 26 #include <microhttpd.h> /* just for HTTP status codes */ 27 #include <gnunet/gnunet_util_lib.h> 28 #include <gnunet/gnunet_curl_lib.h> 29 #include <taler/merchant/post-private-orders-ORDER_ID-collect.h> 30 #include "merchant_api_curl_defaults.h" 31 #include "merchant_api_common.h" 32 #include <taler/taler_json_lib.h> 33 #include <taler/taler_curl_lib.h> 34 35 36 /** 37 * Handle for a POST /private/orders/$ORDER_ID/collect operation. 38 */ 39 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle 40 { 41 /** 42 * Base URL of the merchant backend. 43 */ 44 char *base_url; 45 46 /** 47 * The full URL for this request. 48 */ 49 char *url; 50 51 /** 52 * Handle for the request. 53 */ 54 struct GNUNET_CURL_Job *job; 55 56 /** 57 * Function to call with the result. 58 */ 59 TALER_MERCHANT_PostPrivateOrdersCollectCallback cb; 60 61 /** 62 * Closure for @a cb. 63 */ 64 TALER_MERCHANT_POST_PRIVATE_ORDERS_COLLECT_RESULT_CLOSURE *cb_cls; 65 66 /** 67 * Reference to the execution context. 68 */ 69 struct GNUNET_CURL_Context *ctx; 70 71 /** 72 * Minor context that holds body and headers. 73 */ 74 struct TALER_CURL_PostContext post_ctx; 75 76 /** 77 * Identifier of the order to collect. 78 */ 79 char *order_id; 80 81 /** 82 * Session ID to store with the payment, NULL for none. 83 */ 84 char *session_id; 85 86 /** 87 * Choice to complete, negative if the contract has no choices. 88 */ 89 int16_t choice_index; 90 }; 91 92 93 /** 94 * Function called when we're done processing the 95 * HTTP POST /private/orders/$ORDER_ID/collect request. 96 * 97 * @param cls the `struct TALER_MERCHANT_PostPrivateOrdersCollectHandle` 98 * @param response_code HTTP response code, 0 on error 99 * @param response response body, NULL if not in JSON 100 */ 101 static void 102 handle_collect_finished (void *cls, 103 long response_code, 104 const void *response) 105 { 106 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch = cls; 107 const json_t *json = response; 108 struct TALER_MERCHANT_PostPrivateOrdersCollectResponse cr = { 109 .hr.http_status = (unsigned int) response_code, 110 .hr.reply = json 111 }; 112 113 poch->job = NULL; 114 switch (response_code) 115 { 116 case 0: 117 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 118 break; 119 case MHD_HTTP_OK: 120 { 121 struct GNUNET_JSON_Specification spec[] = { 122 GNUNET_JSON_spec_fixed_auto ("sig", 123 &cr.details.ok.merchant_sig), 124 GNUNET_JSON_spec_mark_optional ( 125 GNUNET_JSON_spec_string ("pos_confirmation", 126 &cr.details.ok.pos_confirmation), 127 NULL), 128 GNUNET_JSON_spec_end () 129 }; 130 131 /* The response is the one of paying the order; "token_sigs" is 132 never present, as orders with token outputs cannot be 133 collected. */ 134 if (GNUNET_OK != 135 GNUNET_JSON_parse (json, 136 spec, 137 NULL, NULL)) 138 { 139 GNUNET_break_op (0); 140 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 141 cr.hr.http_status = 0; 142 cr.hr.hint = "sig field missing in response"; 143 } 144 } 145 break; 146 case MHD_HTTP_UNAUTHORIZED: 147 cr.hr.ec = TALER_JSON_get_error_code (json); 148 cr.hr.hint = TALER_JSON_get_error_hint (json); 149 break; 150 case MHD_HTTP_NOT_FOUND: 151 cr.hr.ec = TALER_JSON_get_error_code (json); 152 cr.hr.hint = TALER_JSON_get_error_hint (json); 153 break; 154 case MHD_HTTP_CONFLICT: 155 cr.hr.ec = TALER_JSON_get_error_code (json); 156 cr.hr.hint = TALER_JSON_get_error_hint (json); 157 break; 158 default: 159 GNUNET_break_op (0); 160 TALER_MERCHANT_parse_error_details_ (json, 161 response_code, 162 &cr.hr); 163 break; 164 } 165 poch->cb (poch->cb_cls, 166 &cr); 167 TALER_MERCHANT_post_private_orders_collect_cancel (poch); 168 } 169 170 171 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle * 172 TALER_MERCHANT_post_private_orders_collect_create ( 173 struct GNUNET_CURL_Context *ctx, 174 const char *url, 175 const char *order_id, 176 const char *session_id, 177 int16_t choice_index) 178 { 179 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch; 180 181 poch = GNUNET_new (struct TALER_MERCHANT_PostPrivateOrdersCollectHandle); 182 poch->ctx = ctx; 183 poch->base_url = GNUNET_strdup (url); 184 poch->order_id = GNUNET_strdup (order_id); 185 poch->choice_index = choice_index; 186 if (NULL != session_id) 187 poch->session_id = GNUNET_strdup (session_id); 188 return poch; 189 } 190 191 192 enum TALER_ErrorCode 193 TALER_MERCHANT_post_private_orders_collect_start ( 194 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch, 195 TALER_MERCHANT_PostPrivateOrdersCollectCallback cb, 196 TALER_MERCHANT_POST_PRIVATE_ORDERS_COLLECT_RESULT_CLOSURE *cb_cls) 197 { 198 json_t *req_obj; 199 CURL *eh; 200 201 poch->cb = cb; 202 poch->cb_cls = cb_cls; 203 { 204 char *path; 205 206 GNUNET_asprintf (&path, 207 "private/orders/%s/collect", 208 poch->order_id); 209 poch->url = TALER_url_join (poch->base_url, 210 path, 211 NULL); 212 GNUNET_free (path); 213 } 214 if (NULL == poch->url) 215 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 216 req_obj = GNUNET_JSON_PACK ( 217 GNUNET_JSON_pack_allow_null ( 218 GNUNET_JSON_pack_string ("session_id", 219 poch->session_id))); 220 GNUNET_assert (NULL != req_obj); 221 if (0 <= poch->choice_index) 222 GNUNET_assert (0 == 223 json_object_set_new (req_obj, 224 "choice_index", 225 json_integer (poch->choice_index))); 226 eh = TALER_MERCHANT_curl_easy_get_ (poch->url); 227 if ( (NULL == eh) || 228 (GNUNET_OK != 229 TALER_curl_easy_post (&poch->post_ctx, 230 eh, 231 req_obj)) ) 232 { 233 GNUNET_break (0); 234 json_decref (req_obj); 235 if (NULL != eh) 236 curl_easy_cleanup (eh); 237 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 238 } 239 json_decref (req_obj); 240 poch->job = GNUNET_CURL_job_add2 (poch->ctx, 241 eh, 242 poch->post_ctx.headers, 243 &handle_collect_finished, 244 poch); 245 if (NULL == poch->job) 246 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 247 return TALER_EC_NONE; 248 } 249 250 251 void 252 TALER_MERCHANT_post_private_orders_collect_cancel ( 253 struct TALER_MERCHANT_PostPrivateOrdersCollectHandle *poch) 254 { 255 if (NULL != poch->job) 256 { 257 GNUNET_CURL_job_cancel (poch->job); 258 poch->job = NULL; 259 } 260 TALER_curl_easy_post_finished (&poch->post_ctx); 261 GNUNET_free (poch->order_id); 262 GNUNET_free (poch->session_id); 263 GNUNET_free (poch->url); 264 GNUNET_free (poch->base_url); 265 GNUNET_free (poch); 266 } 267 268 269 /* end of merchant_api_post-private-orders-ORDER_ID-collect.c */