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