taler-merchant-httpd_post-orders-ORDER_ID-claim.c (11756B)
1 /* 2 This file is part of TALER 3 (C) 2014, 2015, 2016, 2018, 2020 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file src/backend/taler-merchant-httpd_post-orders-ORDER_ID-claim.c 22 * @brief headers for POST /orders/$ID/claim handler 23 * @author Marcello Stanisci 24 * @author Christian Grothoff 25 */ 26 #include "platform.h" 27 #include <jansson.h> 28 #include <taler/taler_signatures.h> 29 #include <taler/taler_dbevents.h> 30 #include <taler/taler_json_lib.h> 31 #include "taler-merchant-httpd_get-private-orders.h" 32 #include "taler-merchant-httpd_post-orders-ORDER_ID-claim.h" 33 #include "merchant-database/insert_contract_terms.h" 34 #include "merchant-database/get_contract_terms.h" 35 #include "merchant-database/get_order.h" 36 #include "merchant-database/event_notify.h" 37 #include "merchant-database/preflight.h" 38 #include "merchant-database/start.h" 39 40 41 /** 42 * How often do we retry the database transaction? 43 */ 44 #define MAX_RETRIES 3 45 46 47 /** 48 * Run transaction to claim @a order_id for @a nonce. 49 * 50 * @param hc handler context with information about instance to claim order at 51 * @param order_id order to claim 52 * @param nonce nonce to use for the claim 53 * @param claim_token the token that should be used to verify the claim 54 * @param[out] contract_terms set to the resulting contract terms 55 * (for any non-negative result; 56 * @return transaction status code 57 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the order was claimed by a different 58 * nonce (@a contract_terms set to non-NULL) 59 * OR if the order is is unknown (@a contract_terms is NULL) 60 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the order was successfully claimed 61 */ 62 static enum GNUNET_DB_QueryStatus 63 claim_order (struct TMH_HandlerContext *hc, 64 const char *order_id, 65 const struct GNUNET_CRYPTO_EddsaPublicKey *nonce, 66 const struct TALER_ClaimTokenP *claim_token, 67 json_t **contract_terms) 68 { 69 const char *instance_id = hc->instance->settings.id; 70 struct TALER_ClaimTokenP order_ct; 71 enum GNUNET_DB_QueryStatus qs; 72 uint64_t order_serial; 73 74 if (GNUNET_OK != 75 TALER_MERCHANTDB_start (TMH_db, 76 "claim order")) 77 { 78 GNUNET_break (0); 79 return GNUNET_DB_STATUS_HARD_ERROR; 80 } 81 qs = TALER_MERCHANTDB_get_contract_terms (TMH_db, 82 instance_id, 83 order_id, 84 contract_terms, 85 &order_serial, 86 NULL); 87 if (0 > qs) 88 { 89 TALER_MERCHANTDB_rollback (TMH_db); 90 return qs; 91 } 92 93 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 94 { 95 /* We already have claimed contract terms for this order_id */ 96 struct GNUNET_CRYPTO_EddsaPublicKey stored_nonce; 97 struct GNUNET_JSON_Specification spec[] = { 98 GNUNET_JSON_spec_fixed_auto ("nonce", 99 &stored_nonce), 100 GNUNET_JSON_spec_end () 101 }; 102 103 TALER_MERCHANTDB_rollback (TMH_db); 104 GNUNET_assert (NULL != *contract_terms); 105 106 if (GNUNET_OK != 107 GNUNET_JSON_parse (*contract_terms, 108 spec, 109 NULL, 110 NULL)) 111 { 112 /* this should not be possible: contract_terms should always 113 have a nonce! */ 114 GNUNET_break (0); 115 json_decref (*contract_terms); 116 *contract_terms = NULL; 117 return GNUNET_DB_STATUS_HARD_ERROR; 118 } 119 120 if (0 != 121 GNUNET_memcmp (&stored_nonce, 122 nonce)) 123 { 124 GNUNET_JSON_parse_free (spec); 125 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 126 } 127 GNUNET_JSON_parse_free (spec); 128 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 129 } 130 131 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 132 133 /* Now we need to claim the order. */ 134 { 135 struct TALER_MerchantPostDataHashP unused; 136 struct GNUNET_TIME_Timestamp timestamp; 137 struct GNUNET_JSON_Specification spec[] = { 138 GNUNET_JSON_spec_timestamp ("timestamp", 139 ×tamp), 140 GNUNET_JSON_spec_end () 141 }; 142 143 /* see if we have this order in our table of unclaimed orders */ 144 qs = TALER_MERCHANTDB_get_order (TMH_db, 145 instance_id, 146 order_id, 147 &order_ct, 148 &unused, 149 contract_terms); 150 if (0 >= qs) 151 { 152 TALER_MERCHANTDB_rollback (TMH_db); 153 return qs; 154 } 155 GNUNET_assert (NULL != *contract_terms); 156 if (GNUNET_OK != 157 GNUNET_JSON_parse (*contract_terms, 158 spec, 159 NULL, 160 NULL)) 161 { 162 /* this should not be possible: contract_terms should always 163 have a timestamp! */ 164 GNUNET_break (0); 165 TALER_MERCHANTDB_rollback (TMH_db); 166 json_decref (*contract_terms); 167 *contract_terms = NULL; 168 return GNUNET_DB_STATUS_HARD_ERROR; 169 } 170 171 GNUNET_assert (0 == 172 json_object_set_new ( 173 *contract_terms, 174 "nonce", 175 GNUNET_JSON_from_data_auto (nonce))); 176 if (0 != GNUNET_memcmp_priv (&order_ct, 177 claim_token)) 178 { 179 TALER_MERCHANTDB_rollback (TMH_db); 180 json_decref (*contract_terms); 181 *contract_terms = NULL; 182 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 183 } 184 qs = TALER_MERCHANTDB_insert_contract_terms (TMH_db, 185 instance_id, 186 order_id, 187 *contract_terms, 188 &order_serial); 189 if (0 >= qs) 190 { 191 TALER_MERCHANTDB_rollback (TMH_db); 192 json_decref (*contract_terms); 193 *contract_terms = NULL; 194 return qs; 195 } 196 // FIXME: unify notifications? or do we need both? 197 TMH_notify_order_change (TMH_lookup_instance (instance_id), 198 TMH_OSF_CLAIMED, 199 timestamp, 200 order_serial); 201 { 202 struct TMH_OrderPayEventP pay_eh = { 203 .header.size = htons (sizeof (pay_eh)), 204 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED), 205 .merchant_pub = hc->instance->merchant_pub 206 }; 207 208 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 209 "Notifying clients about status change of order %s\n", 210 order_id); 211 GNUNET_CRYPTO_hash (order_id, 212 strlen (order_id), 213 &pay_eh.h_order_id); 214 TALER_MERCHANTDB_event_notify (TMH_db, 215 &pay_eh.header, 216 NULL, 217 0); 218 } 219 qs = TALER_MERCHANTDB_commit (TMH_db); 220 if (0 > qs) 221 return qs; 222 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 223 } 224 } 225 226 227 enum MHD_Result 228 TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh, 229 struct MHD_Connection *connection, 230 struct TMH_HandlerContext *hc) 231 { 232 const char *order_id = hc->infix; 233 struct GNUNET_CRYPTO_EddsaPublicKey nonce; 234 enum GNUNET_DB_QueryStatus qs; 235 json_t *contract_terms; 236 struct TALER_ClaimTokenP claim_token = { 0 }; 237 238 { 239 struct GNUNET_JSON_Specification spec[] = { 240 GNUNET_JSON_spec_fixed_auto ("nonce", 241 &nonce), 242 GNUNET_JSON_spec_mark_optional ( 243 GNUNET_JSON_spec_fixed_auto ("token", 244 &claim_token), 245 NULL), 246 GNUNET_JSON_spec_end () 247 }; 248 enum GNUNET_GenericReturnValue res; 249 250 res = TALER_MHD_parse_json_data (connection, 251 hc->request_body, 252 spec); 253 if (GNUNET_OK != res) 254 { 255 GNUNET_break_op (0); 256 json_dumpf (hc->request_body, 257 stderr, 258 JSON_INDENT (2)); 259 return (GNUNET_NO == res) 260 ? MHD_YES 261 : MHD_NO; 262 } 263 } 264 contract_terms = NULL; 265 for (unsigned int i = 0; i<MAX_RETRIES; i++) 266 { 267 TALER_MERCHANTDB_preflight (TMH_db); 268 qs = claim_order (hc, 269 order_id, 270 &nonce, 271 &claim_token, 272 &contract_terms); 273 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 274 break; 275 } 276 switch (qs) 277 { 278 case GNUNET_DB_STATUS_HARD_ERROR: 279 return TALER_MHD_reply_with_error (connection, 280 MHD_HTTP_INTERNAL_SERVER_ERROR, 281 TALER_EC_GENERIC_DB_COMMIT_FAILED, 282 NULL); 283 case GNUNET_DB_STATUS_SOFT_ERROR: 284 return TALER_MHD_reply_with_error (connection, 285 MHD_HTTP_INTERNAL_SERVER_ERROR, 286 TALER_EC_GENERIC_DB_SOFT_FAILURE, 287 NULL); 288 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 289 if (NULL == contract_terms) 290 return TALER_MHD_reply_with_error (connection, 291 MHD_HTTP_NOT_FOUND, 292 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND, 293 order_id); 294 /* already claimed! */ 295 json_decref (contract_terms); 296 return TALER_MHD_reply_with_error (connection, 297 MHD_HTTP_CONFLICT, 298 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED, 299 order_id); 300 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 301 GNUNET_assert (NULL != contract_terms); 302 break; /* Good! return signature (below) */ 303 } 304 305 /* create contract signature */ 306 { 307 struct TALER_PrivateContractHashP hash; 308 struct TALER_MerchantSignatureP merchant_sig; 309 310 /** 311 * Hash of the JSON contract in UTF-8 including 0-termination, 312 * using JSON_COMPACT | JSON_SORT_KEYS 313 */ 314 315 if (GNUNET_OK != 316 TALER_JSON_contract_hash (contract_terms, 317 &hash)) 318 { 319 GNUNET_break (0); 320 json_decref (contract_terms); 321 return TALER_MHD_reply_with_error (connection, 322 MHD_HTTP_INTERNAL_SERVER_ERROR, 323 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 324 NULL); 325 } 326 327 TALER_merchant_contract_sign (&hash, 328 &hc->instance->merchant_priv, 329 &merchant_sig); 330 return TALER_MHD_REPLY_JSON_PACK ( 331 connection, 332 MHD_HTTP_OK, 333 GNUNET_JSON_pack_object_steal ("contract_terms", 334 contract_terms), 335 GNUNET_JSON_pack_data_auto ("sig", 336 &merchant_sig)); 337 } 338 } 339 340 341 /* end of taler-merchant-httpd_post-orders-ORDER_ID-claim.c */