taler-merchant-httpd_post-orders-ORDER_ID-claim.c (11664B)
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/lookup_contract_terms.h" 35 #include "merchant-database/lookup_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_lookup_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 return GNUNET_DB_STATUS_HARD_ERROR; 116 } 117 118 if (0 != 119 GNUNET_memcmp (&stored_nonce, 120 nonce)) 121 { 122 GNUNET_JSON_parse_free (spec); 123 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 124 } 125 GNUNET_JSON_parse_free (spec); 126 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 127 } 128 129 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 130 131 /* Now we need to claim the order. */ 132 { 133 struct TALER_MerchantPostDataHashP unused; 134 struct GNUNET_TIME_Timestamp timestamp; 135 struct GNUNET_JSON_Specification spec[] = { 136 GNUNET_JSON_spec_timestamp ("timestamp", 137 ×tamp), 138 GNUNET_JSON_spec_end () 139 }; 140 141 /* see if we have this order in our table of unclaimed orders */ 142 qs = TALER_MERCHANTDB_lookup_order (TMH_db, 143 instance_id, 144 order_id, 145 &order_ct, 146 &unused, 147 contract_terms); 148 if (0 >= qs) 149 { 150 TALER_MERCHANTDB_rollback (TMH_db); 151 return qs; 152 } 153 GNUNET_assert (NULL != *contract_terms); 154 if (GNUNET_OK != 155 GNUNET_JSON_parse (*contract_terms, 156 spec, 157 NULL, 158 NULL)) 159 { 160 /* this should not be possible: contract_terms should always 161 have a timestamp! */ 162 GNUNET_break (0); 163 TALER_MERCHANTDB_rollback (TMH_db); 164 return GNUNET_DB_STATUS_HARD_ERROR; 165 } 166 167 GNUNET_assert (0 == 168 json_object_set_new ( 169 *contract_terms, 170 "nonce", 171 GNUNET_JSON_from_data_auto (nonce))); 172 if (0 != GNUNET_memcmp_priv (&order_ct, 173 claim_token)) 174 { 175 TALER_MERCHANTDB_rollback (TMH_db); 176 json_decref (*contract_terms); 177 *contract_terms = NULL; 178 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 179 } 180 qs = TALER_MERCHANTDB_insert_contract_terms (TMH_db, 181 instance_id, 182 order_id, 183 *contract_terms, 184 &order_serial); 185 if (0 >= qs) 186 { 187 TALER_MERCHANTDB_rollback (TMH_db); 188 json_decref (*contract_terms); 189 *contract_terms = NULL; 190 return qs; 191 } 192 // FIXME: unify notifications? or do we need both? 193 TMH_notify_order_change (TMH_lookup_instance (instance_id), 194 TMH_OSF_CLAIMED, 195 timestamp, 196 order_serial); 197 { 198 struct TMH_OrderPayEventP pay_eh = { 199 .header.size = htons (sizeof (pay_eh)), 200 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED), 201 .merchant_pub = hc->instance->merchant_pub 202 }; 203 204 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 205 "Notifying clients about status change of order %s\n", 206 order_id); 207 GNUNET_CRYPTO_hash (order_id, 208 strlen (order_id), 209 &pay_eh.h_order_id); 210 TALER_MERCHANTDB_event_notify (TMH_db, 211 &pay_eh.header, 212 NULL, 213 0); 214 } 215 qs = TALER_MERCHANTDB_commit (TMH_db); 216 if (0 > qs) 217 return qs; 218 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 219 } 220 } 221 222 223 enum MHD_Result 224 TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh, 225 struct MHD_Connection *connection, 226 struct TMH_HandlerContext *hc) 227 { 228 const char *order_id = hc->infix; 229 struct GNUNET_CRYPTO_EddsaPublicKey nonce; 230 enum GNUNET_DB_QueryStatus qs; 231 json_t *contract_terms; 232 struct TALER_ClaimTokenP claim_token = { 0 }; 233 234 { 235 struct GNUNET_JSON_Specification spec[] = { 236 GNUNET_JSON_spec_fixed_auto ("nonce", 237 &nonce), 238 GNUNET_JSON_spec_mark_optional ( 239 GNUNET_JSON_spec_fixed_auto ("token", 240 &claim_token), 241 NULL), 242 GNUNET_JSON_spec_end () 243 }; 244 enum GNUNET_GenericReturnValue res; 245 246 res = TALER_MHD_parse_json_data (connection, 247 hc->request_body, 248 spec); 249 if (GNUNET_OK != res) 250 { 251 GNUNET_break_op (0); 252 json_dumpf (hc->request_body, 253 stderr, 254 JSON_INDENT (2)); 255 return (GNUNET_NO == res) 256 ? MHD_YES 257 : MHD_NO; 258 } 259 } 260 contract_terms = NULL; 261 for (unsigned int i = 0; i<MAX_RETRIES; i++) 262 { 263 TALER_MERCHANTDB_preflight (TMH_db); 264 qs = claim_order (hc, 265 order_id, 266 &nonce, 267 &claim_token, 268 &contract_terms); 269 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 270 break; 271 } 272 switch (qs) 273 { 274 case GNUNET_DB_STATUS_HARD_ERROR: 275 return TALER_MHD_reply_with_error (connection, 276 MHD_HTTP_INTERNAL_SERVER_ERROR, 277 TALER_EC_GENERIC_DB_COMMIT_FAILED, 278 NULL); 279 case GNUNET_DB_STATUS_SOFT_ERROR: 280 return TALER_MHD_reply_with_error (connection, 281 MHD_HTTP_INTERNAL_SERVER_ERROR, 282 TALER_EC_GENERIC_DB_SOFT_FAILURE, 283 NULL); 284 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 285 if (NULL == contract_terms) 286 return TALER_MHD_reply_with_error (connection, 287 MHD_HTTP_NOT_FOUND, 288 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND, 289 order_id); 290 /* already claimed! */ 291 json_decref (contract_terms); 292 return TALER_MHD_reply_with_error (connection, 293 MHD_HTTP_CONFLICT, 294 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED, 295 order_id); 296 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 297 GNUNET_assert (NULL != contract_terms); 298 break; /* Good! return signature (below) */ 299 } 300 301 /* create contract signature */ 302 { 303 struct TALER_PrivateContractHashP hash; 304 struct TALER_MerchantSignatureP merchant_sig; 305 306 /** 307 * Hash of the JSON contract in UTF-8 including 0-termination, 308 * using JSON_COMPACT | JSON_SORT_KEYS 309 */ 310 311 if (GNUNET_OK != 312 TALER_JSON_contract_hash (contract_terms, 313 &hash)) 314 { 315 GNUNET_break (0); 316 json_decref (contract_terms); 317 return TALER_MHD_reply_with_error (connection, 318 MHD_HTTP_INTERNAL_SERVER_ERROR, 319 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 320 NULL); 321 } 322 323 TALER_merchant_contract_sign (&hash, 324 &hc->instance->merchant_priv, 325 &merchant_sig); 326 return TALER_MHD_REPLY_JSON_PACK ( 327 connection, 328 MHD_HTTP_OK, 329 GNUNET_JSON_pack_object_steal ("contract_terms", 330 contract_terms), 331 GNUNET_JSON_pack_data_auto ("sig", 332 &merchant_sig)); 333 } 334 } 335 336 337 /* end of taler-merchant-httpd_post-orders-ORDER_ID-claim.c */