taler-merchant-httpd_post-orders-ORDER_ID-claim.c (11167B)
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 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 "taler/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 34 35 /** 36 * How often do we retry the database transaction? 37 */ 38 #define MAX_RETRIES 3 39 40 41 /** 42 * Run transaction to claim @a order_id for @a nonce. 43 * 44 * @param hc handler context with information about instance to claim order at 45 * @param order_id order to claim 46 * @param nonce nonce to use for the claim 47 * @param claim_token the token that should be used to verify the claim 48 * @param[out] contract_terms set to the resulting contract terms 49 * (for any non-negative result; 50 * @return transaction status code 51 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the order was claimed by a different 52 * nonce (@a contract_terms set to non-NULL) 53 * OR if the order is is unknown (@a contract_terms is NULL) 54 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the order was successfully claimed 55 */ 56 static enum GNUNET_DB_QueryStatus 57 claim_order (struct TMH_HandlerContext *hc, 58 const char *order_id, 59 const struct GNUNET_CRYPTO_EddsaPublicKey *nonce, 60 const struct TALER_ClaimTokenP *claim_token, 61 json_t **contract_terms) 62 { 63 const char *instance_id = hc->instance->settings.id; 64 struct TALER_ClaimTokenP order_ct; 65 enum GNUNET_DB_QueryStatus qs; 66 uint64_t order_serial; 67 68 if (GNUNET_OK != 69 TMH_db->start (TMH_db->cls, 70 "claim order")) 71 { 72 GNUNET_break (0); 73 return GNUNET_DB_STATUS_HARD_ERROR; 74 } 75 qs = TMH_db->lookup_contract_terms (TMH_db->cls, 76 instance_id, 77 order_id, 78 contract_terms, 79 &order_serial, 80 NULL); 81 if (0 > qs) 82 { 83 TMH_db->rollback (TMH_db->cls); 84 return qs; 85 } 86 87 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 88 { 89 /* We already have claimed contract terms for this order_id */ 90 struct GNUNET_CRYPTO_EddsaPublicKey stored_nonce; 91 struct GNUNET_JSON_Specification spec[] = { 92 GNUNET_JSON_spec_fixed_auto ("nonce", 93 &stored_nonce), 94 GNUNET_JSON_spec_end () 95 }; 96 97 TMH_db->rollback (TMH_db->cls); 98 GNUNET_assert (NULL != *contract_terms); 99 100 if (GNUNET_OK != 101 GNUNET_JSON_parse (*contract_terms, 102 spec, 103 NULL, 104 NULL)) 105 { 106 /* this should not be possible: contract_terms should always 107 have a nonce! */ 108 GNUNET_break (0); 109 return GNUNET_DB_STATUS_HARD_ERROR; 110 } 111 112 if (0 != 113 GNUNET_memcmp (&stored_nonce, 114 nonce)) 115 { 116 GNUNET_JSON_parse_free (spec); 117 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 118 } 119 GNUNET_JSON_parse_free (spec); 120 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 121 } 122 123 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); 124 125 /* Now we need to claim the order. */ 126 { 127 struct TALER_MerchantPostDataHashP unused; 128 struct GNUNET_TIME_Timestamp timestamp; 129 struct GNUNET_JSON_Specification spec[] = { 130 GNUNET_JSON_spec_timestamp ("timestamp", 131 ×tamp), 132 GNUNET_JSON_spec_end () 133 }; 134 135 /* see if we have this order in our table of unclaimed orders */ 136 qs = TMH_db->lookup_order (TMH_db->cls, 137 instance_id, 138 order_id, 139 &order_ct, 140 &unused, 141 contract_terms); 142 if (0 >= qs) 143 { 144 TMH_db->rollback (TMH_db->cls); 145 return qs; 146 } 147 GNUNET_assert (NULL != *contract_terms); 148 if (GNUNET_OK != 149 GNUNET_JSON_parse (*contract_terms, 150 spec, 151 NULL, 152 NULL)) 153 { 154 /* this should not be possible: contract_terms should always 155 have a timestamp! */ 156 GNUNET_break (0); 157 TMH_db->rollback (TMH_db->cls); 158 return GNUNET_DB_STATUS_HARD_ERROR; 159 } 160 161 GNUNET_assert (0 == 162 json_object_set_new ( 163 *contract_terms, 164 "nonce", 165 GNUNET_JSON_from_data_auto (nonce))); 166 if (0 != GNUNET_memcmp_priv (&order_ct, 167 claim_token)) 168 { 169 TMH_db->rollback (TMH_db->cls); 170 json_decref (*contract_terms); 171 *contract_terms = NULL; 172 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 173 } 174 qs = TMH_db->insert_contract_terms (TMH_db->cls, 175 instance_id, 176 order_id, 177 *contract_terms, 178 &order_serial); 179 if (0 >= qs) 180 { 181 TMH_db->rollback (TMH_db->cls); 182 json_decref (*contract_terms); 183 *contract_terms = NULL; 184 return qs; 185 } 186 // FIXME: unify notifications? or do we need both? 187 TMH_notify_order_change (TMH_lookup_instance (instance_id), 188 TMH_OSF_CLAIMED, 189 timestamp, 190 order_serial); 191 { 192 struct TMH_OrderPayEventP pay_eh = { 193 .header.size = htons (sizeof (pay_eh)), 194 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED), 195 .merchant_pub = hc->instance->merchant_pub 196 }; 197 198 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 199 "Notifying clients about status change of order %s\n", 200 order_id); 201 GNUNET_CRYPTO_hash (order_id, 202 strlen (order_id), 203 &pay_eh.h_order_id); 204 TMH_db->event_notify (TMH_db->cls, 205 &pay_eh.header, 206 NULL, 207 0); 208 } 209 qs = TMH_db->commit (TMH_db->cls); 210 if (0 > qs) 211 return qs; 212 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 213 } 214 } 215 216 217 MHD_RESULT 218 TMH_post_orders_ID_claim (const struct TMH_RequestHandler *rh, 219 struct MHD_Connection *connection, 220 struct TMH_HandlerContext *hc) 221 { 222 const char *order_id = hc->infix; 223 struct GNUNET_CRYPTO_EddsaPublicKey nonce; 224 enum GNUNET_DB_QueryStatus qs; 225 json_t *contract_terms; 226 struct TALER_ClaimTokenP claim_token = { 0 }; 227 228 { 229 struct GNUNET_JSON_Specification spec[] = { 230 GNUNET_JSON_spec_fixed_auto ("nonce", 231 &nonce), 232 GNUNET_JSON_spec_mark_optional ( 233 GNUNET_JSON_spec_fixed_auto ("token", 234 &claim_token), 235 NULL), 236 GNUNET_JSON_spec_end () 237 }; 238 enum GNUNET_GenericReturnValue res; 239 240 res = TALER_MHD_parse_json_data (connection, 241 hc->request_body, 242 spec); 243 if (GNUNET_OK != res) 244 { 245 GNUNET_break_op (0); 246 json_dumpf (hc->request_body, 247 stderr, 248 JSON_INDENT (2)); 249 return (GNUNET_NO == res) 250 ? MHD_YES 251 : MHD_NO; 252 } 253 } 254 contract_terms = NULL; 255 for (unsigned int i = 0; i<MAX_RETRIES; i++) 256 { 257 TMH_db->preflight (TMH_db->cls); 258 qs = claim_order (hc, 259 order_id, 260 &nonce, 261 &claim_token, 262 &contract_terms); 263 if (GNUNET_DB_STATUS_SOFT_ERROR != qs) 264 break; 265 } 266 switch (qs) 267 { 268 case GNUNET_DB_STATUS_HARD_ERROR: 269 return TALER_MHD_reply_with_error (connection, 270 MHD_HTTP_INTERNAL_SERVER_ERROR, 271 TALER_EC_GENERIC_DB_COMMIT_FAILED, 272 NULL); 273 case GNUNET_DB_STATUS_SOFT_ERROR: 274 return TALER_MHD_reply_with_error (connection, 275 MHD_HTTP_INTERNAL_SERVER_ERROR, 276 TALER_EC_GENERIC_DB_SOFT_FAILURE, 277 NULL); 278 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 279 if (NULL == contract_terms) 280 return TALER_MHD_reply_with_error (connection, 281 MHD_HTTP_NOT_FOUND, 282 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_NOT_FOUND, 283 order_id); 284 /* already claimed! */ 285 json_decref (contract_terms); 286 return TALER_MHD_reply_with_error (connection, 287 MHD_HTTP_CONFLICT, 288 TALER_EC_MERCHANT_POST_ORDERS_ID_CLAIM_ALREADY_CLAIMED, 289 order_id); 290 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 291 GNUNET_assert (NULL != contract_terms); 292 break; /* Good! return signature (below) */ 293 } 294 295 /* create contract signature */ 296 { 297 struct TALER_PrivateContractHashP hash; 298 struct TALER_MerchantSignatureP merchant_sig; 299 300 /** 301 * Hash of the JSON contract in UTF-8 including 0-termination, 302 * using JSON_COMPACT | JSON_SORT_KEYS 303 */ 304 305 if (GNUNET_OK != 306 TALER_JSON_contract_hash (contract_terms, 307 &hash)) 308 { 309 GNUNET_break (0); 310 json_decref (contract_terms); 311 return TALER_MHD_reply_with_error (connection, 312 MHD_HTTP_INTERNAL_SERVER_ERROR, 313 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 314 NULL); 315 } 316 317 TALER_merchant_contract_sign (&hash, 318 &hc->instance->merchant_priv, 319 &merchant_sig); 320 return TALER_MHD_REPLY_JSON_PACK ( 321 connection, 322 MHD_HTTP_OK, 323 GNUNET_JSON_pack_object_steal ("contract_terms", 324 contract_terms), 325 GNUNET_JSON_pack_data_auto ("sig", 326 &merchant_sig)); 327 } 328 } 329 330 331 /* end of taler-merchant-httpd_post-orders-ORDER_ID-claim.c */