merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit ff8fdfa0ec4f617993d385bb4258fb0538156c20
parent 500b6456966cdcce2ed10a92870f5a1c82e34168
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 15 Oct 2016 03:03:57 +0200

fixing #4577, as well as a few other cases where we need to index by merchant_pub as well as transaction ID; changes APIs in a few places

Diffstat:
Msrc/backend/taler-merchant-httpd.c | 52++++++++++++++++++++++++++++++++--------------------
Msrc/backend/taler-merchant-httpd.h | 19+++++++++++++++----
Msrc/backend/taler-merchant-httpd_pay.c | 644++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backend/taler-merchant-httpd_track-transaction.c | 44++++++++++++++++++++++++++++++++++++--------
Msrc/backend/taler-merchant-httpd_track-transfer.c | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/backenddb/plugin_merchantdb_postgres.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/backenddb/test_merchantdb.c | 1+
Msrc/include/taler_merchantdb_plugin.h | 30++++++++++++++++++++++++++++--
Msrc/lib/merchant_api_track_transfer.c | 67++++---------------------------------------------------------------
9 files changed, 658 insertions(+), 460 deletions(-)

diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -237,7 +237,7 @@ url_handler (void *cls, /** * Callback that frees all the elements in the hashmap - * + * * @param cls closure * @param key current key * @param value current value @@ -564,6 +564,32 @@ instances_iterator_cb (void *cls, } } + +/** + * Lookup a merchant instance by its name. + * + * @param name name of the instance to resolve + * @return NULL if that instance is unknown to us + */ +struct MerchantInstance * +TMH_lookup_instance (const char *name) +{ + struct GNUNET_HashCode h_receiver; + + GNUNET_CRYPTO_hash (name, + strlen (name), + &h_receiver); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Looking for by-id key %s of '%s' in hashmap\n", + GNUNET_h2s (&h_receiver), + name); + /* We're fine if that returns NULL, the calling routine knows how + to handle that */ + return GNUNET_CONTAINER_multihashmap_get (by_id_map, + &h_receiver); +} + + /** * Extract merchant instance from the given JSON * @@ -582,29 +608,15 @@ get_instance (struct json_t *json) { struct json_t *receiver; const char *receiver_str; - struct GNUNET_HashCode h_receiver; - struct MerchantInstance *ret; - /*FIXME who decrefs receiver?*/ if (NULL == (receiver = json_object_get (json, "receiver"))) - receiver = json_string ("default"); - - receiver_str = json_string_value (receiver); - GNUNET_CRYPTO_hash (receiver_str, - strlen (receiver_str), - &h_receiver); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Looking for by-id key %s of '%s' in hashmap\n", - GNUNET_h2s (&h_receiver), - receiver_str); - /* We're fine if that returns NULL, the calling routine knows how - to handle that */ - ret = GNUNET_CONTAINER_multihashmap_get (by_id_map, - &h_receiver); - GNUNET_break (NULL != ret); - return ret; + receiver_str = "default"; + else + receiver_str = json_string_value (receiver); + return TMH_lookup_instance (receiver_str); } + /** * Iterate over each merchant instance, in order to populate * each instance's own data diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h @@ -33,6 +33,7 @@ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ } while (0) + /** * Used by the iterator of the various merchant's instances given * in configuration @@ -42,7 +43,7 @@ struct IterateInstancesCls { /** * Handle for the configuration beig parsed */ - const struct GNUNET_CONFIGURATION_Handle *config; + const struct GNUNET_CONFIGURATION_Handle *config; /** * Current index in the global array of #MerchantInstance @@ -54,7 +55,7 @@ struct IterateInstancesCls { /** * Flag indicating whether config contains a default instance */ - unsigned int default_instance; + unsigned int default_instance; /** * Wire plugin @@ -92,7 +93,7 @@ struct MerchantInstance { * Wire details for this instance */ struct json_t *j_wire; - + /** * Hash of our wire format details as given in #j_wire. */ @@ -102,7 +103,7 @@ struct MerchantInstance { * Merchant's private key */ struct TALER_MerchantPrivateKeyP privkey; - + /** * Merchant's public key */ @@ -255,4 +256,14 @@ extern struct GNUNET_TIME_Relative wire_transfer_delay; void TMH_trigger_daemon (void); +/** + * Lookup a merchant instance by its name. + * + * @param name name of the instance to resolve + * @return NULL if that instance is unknown to us + */ +struct MerchantInstance * +TMH_lookup_instance (const char *name); + + #endif diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c @@ -409,7 +409,7 @@ deposit_cb (void *cls, if (0 != pc->pending) return; /* still more to do */ - + mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK); mr.purpose.size = htonl (sizeof (mr)); mr.h_contract = pc->h_contract; @@ -796,326 +796,328 @@ check_coin_paid (void *cls, * @param total_amount total amount we receive for the contract after fees */ static void - check_transaction_exists (void *cls, - uint64_t transaction_id, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const char *exchange_uri, - const struct GNUNET_HashCode *h_contract, - const struct GNUNET_HashCode *h_xwire, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund, - const struct TALER_Amount *total_amount) - { - struct PayContext *pc = cls; - - if ( (0 == memcmp (h_contract, - &pc->h_contract, - sizeof (struct GNUNET_HashCode))) && - (0 == memcmp (h_xwire, - &pc->mi->h_wire, - sizeof (struct GNUNET_HashCode))) && - (timestamp.abs_value_us == pc->timestamp.abs_value_us) && - (refund.abs_value_us == pc->refund_deadline.abs_value_us) && - (0 == TALER_amount_cmp (total_amount, - &pc->amount) ) ) - { - pc->transaction_exits = GNUNET_YES; - } - else - { - GNUNET_break_op (0); - pc->transaction_exits = GNUNET_SYSERR; - } - } - - extern struct MerchantInstance * - get_instance (struct json_t *json); - - /** - * Accomplish this payment. - * - * @param rh context of the handler - * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure - * (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a - * upload_data - * @return MHD result code - */ - int - MH_handler_pay (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size) - { - struct PayContext *pc; - int res; - json_t *root; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "In handler for /pay.\n"); - if (NULL == *connection_cls) - { - pc = GNUNET_new (struct PayContext); - pc->hc.cc = &pay_context_cleanup; - pc->connection = connection; - *connection_cls = pc; - } - else - { - /* not the first call, recover state */ - pc = *connection_cls; - } - if (0 != pc->response_code) - { - /* We are *done* processing the request, just queue the response (!) */ - if (UINT_MAX == pc->response_code) - { - GNUNET_break (0); - return MHD_NO; /* hard error */ - } - res = MHD_queue_response (connection, - pc->response_code, - pc->response); - if (NULL != pc->response) - { - MHD_destroy_response (pc->response); - pc->response = NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Queueing response (%u) for /pay (%s).\n", - (unsigned int) pc->response_code, - res ? "OK" : "FAILED"); - return res; - } - if (NULL != pc->chosen_exchange) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Shouldn't be here. Old MHD version?\n"); - return MHD_YES; - } - res = TMH_PARSE_post_json (connection, - &pc->json_parse_context, - upload_data, - upload_data_size, - &root); - if (GNUNET_SYSERR == res) - { - GNUNET_break (0); - return TMH_RESPONSE_reply_external_error (connection, - "failed to parse JSON body"); - } - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; /* the POST's body has to be further fetched */ - - mi = get_instance (root); - - /* Got the JSON upload, parse it */ - { - json_t *coins; - json_t *coin; - unsigned int coins_index; - struct TALER_MerchantSignatureP merchant_sig; - struct TALER_ContractPS cp; - const char *chosen_exchange; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount ("amount", &pc->amount), - GNUNET_JSON_spec_json ("coins", &coins), - GNUNET_JSON_spec_fixed_auto ("H_contract", &pc->h_contract), - TALER_JSON_spec_amount ("max_fee", &pc->max_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_sig", &merchant_sig), - GNUNET_JSON_spec_string ("exchange", &chosen_exchange), - GNUNET_JSON_spec_absolute_time ("refund_deadline", &pc->refund_deadline), - GNUNET_JSON_spec_absolute_time ("timestamp", &pc->timestamp), - GNUNET_JSON_spec_uint64 ("transaction_id", &pc->transaction_id), - GNUNET_JSON_spec_end() - }; - - res = TMH_PARSE_json_data (connection, - root, - spec); - if (GNUNET_YES != res) - { - json_decref (root); - GNUNET_break (0); - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - } - pc->mi = get_instance (root); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "/pay: picked instance %s\n", - pc->mi->id); - - if (NULL == pc->mi) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Not able to find the specified receiver\n"); - json_decref (root); - return TMH_RESPONSE_reply_external_error (connection, - "Unknown receiver given"); - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "The receiver for this deposit is '%s', whose bank details are '%s'\n", - pc->mi->id, - json_dumps (pc->mi->j_wire, JSON_COMPACT)); - pc->chosen_exchange = GNUNET_strdup (chosen_exchange); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Parsed JSON for /pay.\n"); - cp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); - cp.purpose.size = htonl (sizeof (struct TALER_ContractPS)); - cp.transaction_id = GNUNET_htonll (pc->transaction_id); - TALER_amount_hton (&cp.total_amount, - &pc->amount); - TALER_amount_hton (&cp.max_fee, - &pc->max_fee); - cp.h_contract = pc->h_contract; - cp.merchant_pub = pc->mi->pubkey; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_CONTRACT, - &cp.purpose, - &merchant_sig.eddsa_sig, - &pc->mi->pubkey.eddsa_pub)) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - json_decref (root); - return TMH_RESPONSE_reply_external_error (connection, - "invalid merchant signature supplied"); - } - - /* 'wire_transfer_deadline' is optional, if it is not present, - generate it here; it will be timestamp plus the - wire_transfer_delay supplied in config file */ - if (NULL == json_object_get (root, - "wire_transfer_deadline")) - { - pc->wire_transfer_deadline = GNUNET_TIME_absolute_add (pc->timestamp, - wire_transfer_delay); - if (pc->wire_transfer_deadline.abs_value_us < pc->refund_deadline.abs_value_us) - { - /* Refund value very large, delay wire transfer accordingly */ - pc->wire_transfer_deadline = pc->refund_deadline; - } - } - else - { - struct GNUNET_JSON_Specification espec[] = { - GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline", - &pc->wire_transfer_deadline), - GNUNET_JSON_spec_end() - }; - - res = TMH_PARSE_json_data (connection, - root, - espec); - if (GNUNET_YES != res) - { - GNUNET_JSON_parse_free (spec); - json_decref (root); - GNUNET_break (0); - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - } - if (pc->wire_transfer_deadline.abs_value_us < pc->refund_deadline.abs_value_us) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - json_decref (root); - return TMH_RESPONSE_reply_external_error (connection, - "refund deadline after wire transfer deadline"); - } - } - - - pc->coins_cnt = json_array_size (coins); - if (0 == pc->coins_cnt) - { - GNUNET_JSON_parse_free (spec); - json_decref (root); - return TMH_RESPONSE_reply_external_error (connection, - "no coins given"); - } - /* note: 1 coin = 1 deposit confirmation expected */ - pc->dc = GNUNET_new_array (pc->coins_cnt, - struct DepositConfirmation); - - /* This loop populates the array 'dc' in 'pc' */ - json_array_foreach (coins, coins_index, coin) - { - struct DepositConfirmation *dc = &pc->dc[coins_index]; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_denomination_public_key ("denom_pub", &dc->denom), - TALER_JSON_spec_amount ("f" /* FIXME */, &dc->amount_with_fee), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &dc->coin_pub), - TALER_JSON_spec_denomination_signature ("ub_sig", &dc->ub_sig), - GNUNET_JSON_spec_fixed_auto ("coin_sig", &dc->coin_sig), - GNUNET_JSON_spec_end() - }; - - res = TMH_PARSE_json_data (connection, - coin, - spec); - if (GNUNET_YES != res) - { - GNUNET_JSON_parse_free (spec); - json_decref (root); - GNUNET_break (0); - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - } - - { - char *s; - - s = TALER_amount_to_string (&dc->amount_with_fee); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Coin #%i has f %s\n", - coins_index, - s); - GNUNET_free (s); - } - - dc->index = coins_index; - dc->pc = pc; - } - GNUNET_JSON_parse_free (spec); - } /* end of parsing of JSON upload */ - pc->pending = pc->coins_cnt; - - /* Check if this payment attempt has already succeeded */ - if (GNUNET_SYSERR == - db->find_payments_by_id (db->cls, - pc->transaction_id, - &check_coin_paid, - pc)) - { - GNUNET_break (0); - json_decref (root); - return TMH_RESPONSE_reply_internal_error (connection, - "Merchant database error"); - } - if (0 == pc->pending) - { - struct MHD_Response *resp; - int ret; - - /* Payment succeeded in the past; take short cut - and accept immediately */ - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - MHD_destroy_response (resp); - json_decref (root); - return ret; - } - /* Check if transaction is already known, if not store it. */ - if (GNUNET_SYSERR == - db->find_transaction (db->cls, - pc->transaction_id, - &pc->mi->pubkey, - &check_transaction_exists, +check_transaction_exists (void *cls, + uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *exchange_uri, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_xwire, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund, + const struct TALER_Amount *total_amount) +{ + struct PayContext *pc = cls; + + if ( (0 == memcmp (h_contract, + &pc->h_contract, + sizeof (struct GNUNET_HashCode))) && + (0 == memcmp (h_xwire, + &pc->mi->h_wire, + sizeof (struct GNUNET_HashCode))) && + (timestamp.abs_value_us == pc->timestamp.abs_value_us) && + (refund.abs_value_us == pc->refund_deadline.abs_value_us) && + (0 == TALER_amount_cmp (total_amount, + &pc->amount) ) ) + { + pc->transaction_exits = GNUNET_YES; + } + else + { + GNUNET_break_op (0); + pc->transaction_exits = GNUNET_SYSERR; + } +} + +extern struct MerchantInstance * +get_instance (struct json_t *json); + + +/** + * Accomplish this payment. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure + * (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a + * upload_data + * @return MHD result code + */ +int +MH_handler_pay (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct PayContext *pc; + int res; + json_t *root; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "In handler for /pay.\n"); + if (NULL == *connection_cls) + { + pc = GNUNET_new (struct PayContext); + pc->hc.cc = &pay_context_cleanup; + pc->connection = connection; + *connection_cls = pc; + } + else + { + /* not the first call, recover state */ + pc = *connection_cls; + } + if (0 != pc->response_code) + { + /* We are *done* processing the request, just queue the response (!) */ + if (UINT_MAX == pc->response_code) + { + GNUNET_break (0); + return MHD_NO; /* hard error */ + } + res = MHD_queue_response (connection, + pc->response_code, + pc->response); + if (NULL != pc->response) + { + MHD_destroy_response (pc->response); + pc->response = NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Queueing response (%u) for /pay (%s).\n", + (unsigned int) pc->response_code, + res ? "OK" : "FAILED"); + return res; + } + if (NULL != pc->chosen_exchange) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Shouldn't be here. Old MHD version?\n"); + return MHD_YES; + } + res = TMH_PARSE_post_json (connection, + &pc->json_parse_context, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return TMH_RESPONSE_reply_external_error (connection, + "failed to parse JSON body"); + } + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; /* the POST's body has to be further fetched */ + + mi = get_instance (root); + + /* Got the JSON upload, parse it */ + { + json_t *coins; + json_t *coin; + unsigned int coins_index; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_ContractPS cp; + const char *chosen_exchange; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount ("amount", &pc->amount), + GNUNET_JSON_spec_json ("coins", &coins), + GNUNET_JSON_spec_fixed_auto ("H_contract", &pc->h_contract), + TALER_JSON_spec_amount ("max_fee", &pc->max_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_sig", &merchant_sig), + GNUNET_JSON_spec_string ("exchange", &chosen_exchange), + GNUNET_JSON_spec_absolute_time ("refund_deadline", &pc->refund_deadline), + GNUNET_JSON_spec_absolute_time ("timestamp", &pc->timestamp), + GNUNET_JSON_spec_uint64 ("transaction_id", &pc->transaction_id), + GNUNET_JSON_spec_end() + }; + + res = TMH_PARSE_json_data (connection, + root, + spec); + if (GNUNET_YES != res) + { + json_decref (root); + GNUNET_break (0); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + pc->mi = mi; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "/pay: picked instance %s\n", + pc->mi->id); + + if (NULL == pc->mi) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Not able to find the specified receiver\n"); + json_decref (root); + return TMH_RESPONSE_reply_external_error (connection, + "Unknown receiver given"); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "The receiver for this deposit is '%s', whose bank details are '%s'\n", + pc->mi->id, + json_dumps (pc->mi->j_wire, JSON_COMPACT)); + pc->chosen_exchange = GNUNET_strdup (chosen_exchange); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Parsed JSON for /pay.\n"); + cp.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); + cp.purpose.size = htonl (sizeof (struct TALER_ContractPS)); + cp.transaction_id = GNUNET_htonll (pc->transaction_id); + TALER_amount_hton (&cp.total_amount, + &pc->amount); + TALER_amount_hton (&cp.max_fee, + &pc->max_fee); + cp.h_contract = pc->h_contract; + cp.merchant_pub = pc->mi->pubkey; + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_CONTRACT, + &cp.purpose, + &merchant_sig.eddsa_sig, + &pc->mi->pubkey.eddsa_pub)) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + json_decref (root); + return TMH_RESPONSE_reply_external_error (connection, + "invalid merchant signature supplied"); + } + + /* 'wire_transfer_deadline' is optional, if it is not present, + generate it here; it will be timestamp plus the + wire_transfer_delay supplied in config file */ + if (NULL == json_object_get (root, + "wire_transfer_deadline")) + { + pc->wire_transfer_deadline = GNUNET_TIME_absolute_add (pc->timestamp, + wire_transfer_delay); + if (pc->wire_transfer_deadline.abs_value_us < pc->refund_deadline.abs_value_us) + { + /* Refund value very large, delay wire transfer accordingly */ + pc->wire_transfer_deadline = pc->refund_deadline; + } + } + else + { + struct GNUNET_JSON_Specification espec[] = { + GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline", + &pc->wire_transfer_deadline), + GNUNET_JSON_spec_end() + }; + + res = TMH_PARSE_json_data (connection, + root, + espec); + if (GNUNET_YES != res) + { + GNUNET_JSON_parse_free (spec); + json_decref (root); + GNUNET_break (0); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + if (pc->wire_transfer_deadline.abs_value_us < pc->refund_deadline.abs_value_us) + { + GNUNET_break (0); + GNUNET_JSON_parse_free (spec); + json_decref (root); + return TMH_RESPONSE_reply_external_error (connection, + "refund deadline after wire transfer deadline"); + } + } + + + pc->coins_cnt = json_array_size (coins); + if (0 == pc->coins_cnt) + { + GNUNET_JSON_parse_free (spec); + json_decref (root); + return TMH_RESPONSE_reply_external_error (connection, + "no coins given"); + } + /* note: 1 coin = 1 deposit confirmation expected */ + pc->dc = GNUNET_new_array (pc->coins_cnt, + struct DepositConfirmation); + + /* This loop populates the array 'dc' in 'pc' */ + json_array_foreach (coins, coins_index, coin) + { + struct DepositConfirmation *dc = &pc->dc[coins_index]; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_denomination_public_key ("denom_pub", &dc->denom), + TALER_JSON_spec_amount ("f" /* FIXME */, &dc->amount_with_fee), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &dc->coin_pub), + TALER_JSON_spec_denomination_signature ("ub_sig", &dc->ub_sig), + GNUNET_JSON_spec_fixed_auto ("coin_sig", &dc->coin_sig), + GNUNET_JSON_spec_end() + }; + + res = TMH_PARSE_json_data (connection, + coin, + spec); + if (GNUNET_YES != res) + { + GNUNET_JSON_parse_free (spec); + json_decref (root); + GNUNET_break (0); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + + { + char *s; + + s = TALER_amount_to_string (&dc->amount_with_fee); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Coin #%i has f %s\n", + coins_index, + s); + GNUNET_free (s); + } + + dc->index = coins_index; + dc->pc = pc; + } + GNUNET_JSON_parse_free (spec); + } /* end of parsing of JSON upload */ + pc->pending = pc->coins_cnt; + + /* Check if this payment attempt has already succeeded */ + if (GNUNET_SYSERR == + db->find_payments_by_id (db->cls, + pc->transaction_id, + &mi->pubkey, + &check_coin_paid, + pc)) + { + GNUNET_break (0); + json_decref (root); + return TMH_RESPONSE_reply_internal_error (connection, + "Merchant database error"); + } + if (0 == pc->pending) + { + struct MHD_Response *resp; + int ret; + + /* Payment succeeded in the past; take short cut + and accept immediately */ + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + resp); + MHD_destroy_response (resp); + json_decref (root); + return ret; + } + /* Check if transaction is already known, if not store it. */ + if (GNUNET_SYSERR == + db->find_transaction (db->cls, + pc->transaction_id, + &pc->mi->pubkey, + &check_transaction_exists, pc)) { GNUNET_break (0); diff --git a/src/backend/taler-merchant-httpd_track-transaction.c b/src/backend/taler-merchant-httpd_track-transaction.c @@ -411,18 +411,37 @@ wire_deposits_cb (void *cls, /** + * Closure for #proof_cb(). + */ +struct ProofCheckContext +{ + /** + * Proof returned from #proof_cb. The reference counter was + * increased for this reference and it must thus be freed. + * NULL if we did not find any proof. The JSON should + * match the `TrackTransferResponse` of the exchange API + * (https://api.taler.net/api-exchange.html#tracktransferresponse) + */ + json_t *p_ret; + +}; + + +/** * Function called with information about a wire transfer identifier. * We actually never expect this to be called. * - * @param cls closure + * @param cls closure with a `struct ProofCheckContext` * @param proof proof from exchange about what the wire transfer was for */ static void proof_cb (void *cls, const json_t *proof) { - /* FIXME #4577: store @a proof in @a cls to return with error message */ - GNUNET_break_op (0); + struct ProofCheckContext *pcc = cls; + + GNUNET_break (NULL == pcc->p_ret); + pcc->p_ret = json_incref ((json_t *) proof); } @@ -437,7 +456,8 @@ proof_cb (void *cls, * @param http_status HTTP status code we got, 0 on exchange protocol violation * @param exchange_pub public key of the exchange used for signing @a json * @param json original json reply (may include signatures, those have then been - * validated already) + * validated already), should be a `TrackTransactionResponse` + * from the exchange API * @param wtid wire transfer identifier used by the exchange, NULL if exchange did not * yet execute the transaction * @param execution_time actual or planned execution time for the wire transfer @@ -454,6 +474,7 @@ wtid_cb (void *cls, { struct TrackCoinContext *tcc = cls; struct TrackTransactionContext *tctx = tcc->tctx; + struct ProofCheckContext pcc; tcc->dwh = NULL; if (MHD_HTTP_OK != http_status) @@ -473,7 +494,7 @@ wtid_cb (void *cls, } tctx->current_wtid = *wtid; tctx->current_execution_time = execution_time; - + pcc.p_ret = NULL; if (GNUNET_YES == db->find_proof_by_wtid (db->cls, tctx->exchange_uri, @@ -482,9 +503,15 @@ wtid_cb (void *cls, NULL)) { GNUNET_break_op (0); - /* FIXME #4577: report error: we got this WTID before, and the - transaction was NOT in the list. So exchange is lying to us! - (or our DB is internally inconsistent.) */ + resume_track_transaction_with_response + (tcc->tctx, + MHD_HTTP_CONFLICT, + TMH_RESPONSE_make_json_pack ("{s:s, s:O, s:o, s:o}", + "error", "conflicting transfer data from exchange", + "transaction_tracking_claim", json, + "wtid_tracking_claim", pcc.p_ret, + "coin_pub", GNUNET_JSON_from_data_auto (&tcc->coin_pub))); + return; } tctx->wdh = TALER_EXCHANGE_track_transfer (tctx->eh, wtid, @@ -887,6 +914,7 @@ MH_handler_track_transaction (struct TMH_RequestHandler *rh, } ret = db->find_payments_by_id (db->cls, transaction_id, + &tctx->mi->pubkey, &coin_cb, tctx); if (GNUNET_SYSERR == ret) diff --git a/src/backend/taler-merchant-httpd_track-transfer.c b/src/backend/taler-merchant-httpd_track-transfer.c @@ -60,6 +60,11 @@ struct TrackTransferContext struct TALER_EXCHANGE_TrackTransferHandle *wdh; /** + * For which merchant instance is this tracking request? + */ + struct MerchantInstance *mi; + + /** * HTTP connection we are handling. */ struct MHD_Connection *connection; @@ -98,6 +103,16 @@ struct TrackTransferContext struct TALER_WireTransferIdentifierRawP wtid; /** + * Full original response we are currently processing. + */ + const json_t *original_response; + + /** + * Which transaction detail are we currently looking at? + */ + unsigned int current_offset; + + /** * Response code to return. */ unsigned int response_code; @@ -195,7 +210,7 @@ track_transfer_cleanup (struct TM_HandlerContext *hc) * @param transaction_id of the contract * @param coin_pub public key of the coin * @param amount_with_fee amount the exchange will transfer for this coin - * @param transfer_fee fee the exchange will charge for this coin + * @param deposit_fee fee the exchange will charge for this coin * @param exchange_proof proof from exchange that coin was accepted */ static void @@ -203,25 +218,34 @@ check_transfer (void *cls, uint64_t transaction_id, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_Amount *amount_with_fee, - const struct TALER_Amount *transfer_fee, + const struct TALER_Amount *deposit_fee, const json_t *exchange_proof) { struct TrackTransferContext *rctx = cls; - const struct TALER_TrackTransferDetails *wdd = rctx->current_detail; + const struct TALER_TrackTransferDetails *ttd = rctx->current_detail; - if (0 != memcmp (&wdd->coin_pub, - coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP))) - return; /* not the coin we're looking for */ + if (GNUNET_SYSERR == rctx->check_transfer_result) + return; /* already had a serious issue; odd that we're called more than once as well... */ if ( (0 != TALER_amount_cmp (amount_with_fee, - &wdd->coin_value)) || - (0 != TALER_amount_cmp (transfer_fee, - &wdd->coin_fee)) ) + &ttd->coin_value)) || + (0 != TALER_amount_cmp (deposit_fee, + &ttd->coin_fee)) ) { /* Disagreement between the exchange and us about how much this coin is worth! */ GNUNET_break_op (0); rctx->check_transfer_result = GNUNET_SYSERR; + /* Build the `TrackTransferConflictDetails` */ + rctx->response + = TMH_RESPONSE_make_json_pack ("{s:s, s:O, s:I, s:O, s:o, s:I, s:o, s:o}", + "hint", "disagreement about deposit valuation", + "exchange_deposit_proof", exchange_proof, + "conflict_offset", (json_int_t) rctx->current_offset, + "exchange_transfer_proof", rctx->original_response, + "coin_pub", GNUNET_JSON_from_data_auto (coin_pub), + "transaction_id", (json_int_t) transaction_id, + "amount_with_fee", TALER_JSON_from_amount (amount_with_fee), + "deposit_fee", TALER_JSON_from_amount (deposit_fee)); return; } rctx->check_transfer_result = GNUNET_OK; @@ -284,40 +308,73 @@ wire_transfer_cb (void *cls, { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to persist wire transfer proof in DB\n"); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:s}", + "details", "failed to store response from exchange to local database")); + return; } - + rctx->original_response = json; for (i=0;i<details_length;i++) { + rctx->current_offset = i; rctx->current_detail = &details[i]; rctx->check_transfer_result = GNUNET_NO; - ret = db->find_payments_by_id (db->cls, - details[i].transaction_id, - &check_transfer, - rctx); + ret = db->find_payments_by_id_and_coin (db->cls, + details[i].transaction_id, + &rctx->mi->pubkey, + &details[i].coin_pub, + &check_transfer, + rctx); if (GNUNET_SYSERR == ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to verify existing payment data in DB\n"); + "Failed to obtain existing payment data from DB\n"); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:s}", + "details", "failed to obtain deposit data from local database")); + return; } - if ( (GNUNET_NO == ret) || - (GNUNET_NO == rctx->check_transfer_result) ) + if (GNUNET_NO == ret) { + /* The exchange says we made this deposit, but WE do not + recall making it! Well, let's say thanks and accept the + money! */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to find payment data in DB\n"); + rctx->check_transfer_result = GNUNET_OK; } - if (GNUNET_SYSERR == rctx->check_transfer_result) + if (GNUNET_NO == rctx->check_transfer_result) { - /* #check_transfer() failed, do something! */ + /* Internal error: how can we have called #check_transfer() + but still have no result? */ GNUNET_break (0); - /* FIXME: generate nicer custom response */ resume_track_transfer_with_response (rctx, - MHD_HTTP_FAILED_DEPENDENCY, - TMH_RESPONSE_make_json_pack ("{s:I, s:O}", - "index", (json_int_t) i, - "details", json)); + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:s}", + "details", "internal logic error", + "line", (json_int_t) __LINE__, + "file", __FILE__)); return; } + if (GNUNET_SYSERR == rctx->check_transfer_result) + { + /* #check_transfer() failed, report conflict! */ + GNUNET_break_op (0); + GNUNET_assert (NULL != rctx->response); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_CONFLICT, + rctx->response); + rctx->response = NULL; + return; + } + /* Response is consistent with the /deposit we made, remember + it for future reference */ ret = db->store_coin_to_transfer (db->cls, details[i].transaction_id, &details[i].coin_pub, @@ -326,15 +383,19 @@ wire_transfer_cb (void *cls, { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to persist coin to wire transfer mapping in DB\n"); + resume_track_transfer_with_response + (rctx, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_json_pack ("{s:s}", + "details", "failed to store response from exchange to local database")); + return; } } - /* FIXME: might want a more custom response here... */ + rctx->original_response = NULL; resume_track_transfer_with_response (rctx, MHD_HTTP_OK, - TMH_RESPONSE_make_json_pack ("{s:I, s:O}", - "exchange_status", (json_int_t) http_status, - "details", json)); + TMH_RESPONSE_make_json (json)); } @@ -355,9 +416,9 @@ process_track_transfer_with_exchange (void *cls, rctx->fo = NULL; rctx->eh = eh; rctx->wdh = TALER_EXCHANGE_track_transfer (eh, - &rctx->wtid, - &wire_transfer_cb, - rctx); + &rctx->wtid, + &wire_transfer_cb, + rctx); if (NULL == rctx->wdh) { GNUNET_break (0); @@ -400,7 +461,9 @@ handle_track_transfer_timeout (void *cls) * Generate a response based on the given @a proof. * * @param cls closure - * @param proof proof from exchange about what the wire transfer was for + * @param proof proof from exchange about what the wire transfer was for. + * should match the `TrackTransactionResponse` format + * of the exchange */ static void proof_cb (void *cls, @@ -409,10 +472,7 @@ proof_cb (void *cls, struct TrackTransferContext *rctx = cls; rctx->response_code = MHD_HTTP_OK; - /* FIXME: might want a more custom response here... */ - rctx->response = TMH_RESPONSE_make_json_pack ("{s:I, s:O}", - "exchange_status", (json_int_t) MHD_HTTP_OK, - "details", proof); + rctx->response = TMH_RESPONSE_make_json (proof); } @@ -438,6 +498,7 @@ MH_handler_track_transfer (struct TMH_RequestHandler *rh, struct TrackTransferContext *rctx; const char *str; const char *uri; + const char *receiver_str; int ret; if (NULL == *connection_cls) @@ -492,6 +553,15 @@ MH_handler_track_transfer (struct TMH_RequestHandler *rh, "exchange argument missing"); rctx->uri = GNUNET_strdup (uri); + receiver_str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "instance"); + if (NULL == receiver_str) + receiver_str = "default"; + rctx->mi = TMH_lookup_instance (receiver_str); + if (NULL == rctx->mi) + return TMH_RESPONSE_reply_not_found (connection, + "instance unknown"); str = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "wtid"); diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c @@ -306,8 +306,24 @@ postgres_initialize (void *cls) ",deposit_fee_curr" ",exchange_proof" " FROM merchant_deposits" - " WHERE transaction_id=$1", - 1); + " WHERE transaction_id=$1" + " AND merchant_pub=$2", + 2); + PG_PREPARE (pg, + "find_deposits_by_tid_and_coin", + "SELECT" + " amount_with_fee_val" + ",amount_with_fee_frac" + ",amount_with_fee_curr" + ",deposit_fee_val" + ",deposit_fee_frac" + ",deposit_fee_curr" + ",exchange_proof" + " FROM merchant_deposits" + " WHERE transaction_id=$1" + " AND merchant_pub=$2" + " AND coin_pub=$3", + 3); PG_PREPARE (pg, "find_transfers_by_transaction_id", "SELECT" @@ -611,7 +627,7 @@ postgres_find_transactions_by_date (void *cls, GNUNET_PQ_result_spec_uint64 ("transaction_id", &transaction_id), GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", - &merchant_pub), + &merchant_pub), GNUNET_PQ_result_spec_auto_from_type ("h_contract", &h_contract), GNUNET_PQ_result_spec_auto_from_type ("h_wire", @@ -647,7 +663,7 @@ postgres_find_transactions_by_date (void *cls, } PQclear (result); return n; -} +} /** * Find information about a transaction. @@ -746,10 +762,11 @@ postgres_find_transaction (void *cls, /** - * Lookup information about coin payments by transaction ID. + * Lookup information about coin payments by transaction ID (and @a merchant_pub) * * @param cls closure * @param transaction_id key for the search + * @param merchant_pub public key of the merchant * @param cb function to call with payment data * @param cb_cls closure for @a cb * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, @@ -758,6 +775,7 @@ postgres_find_transaction (void *cls, static int postgres_find_payments_by_id (void *cls, uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, TALER_MERCHANTDB_CoinDepositCallback cb, void *cb_cls) { @@ -767,6 +785,7 @@ postgres_find_payments_by_id (void *cls, struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&transaction_id), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; result = GNUNET_PQ_exec_prepared (pg->conn, @@ -829,6 +848,93 @@ postgres_find_payments_by_id (void *cls, /** + * Lookup information about coin payments by transaction ID. + * + * @param cls closure + * @param transaction_id key for the search + * @param merchant_pub merchant's public key. It's AND'd with @a transaction_id + * in order to find the result. + * @param coin_pub public key to use for the search + * @param cb function to call with payment data + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, + * #GNUNET_SYSERR on hard errors + */ +static int +postgres_find_payments_by_id_and_coin (void *cls, + uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + PGresult *result; + unsigned int i; + + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&transaction_id), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), + GNUNET_PQ_query_param_auto_from_type (coin_pub), + GNUNET_PQ_query_param_end + }; + result = GNUNET_PQ_exec_prepared (pg->conn, + "find_deposits_by_tid_and_coin", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + for (i=0;i<PQntuples (result);i++) + { + struct TALER_Amount amount_with_fee; + struct TALER_Amount deposit_fee; + json_t *exchange_proof; + + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_result_spec_amount ("amount_with_fee", + &amount_with_fee), + TALER_PQ_result_spec_amount ("deposit_fee", + &deposit_fee), + TALER_PQ_result_spec_json ("exchange_proof", + &exchange_proof), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + cb (cb_cls, + transaction_id, + coin_pub, + &amount_with_fee, + &deposit_fee, + exchange_proof); + GNUNET_PQ_cleanup_result (rs); + } + PQclear (result); + return GNUNET_OK; + + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +/** * Lookup information about a transfer by @a transaction_id. Note * that in theory there could be multiple wire transfers for a * single @a transaction_id, as the transaction may have involved @@ -1118,6 +1224,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->find_transaction = &postgres_find_transaction; plugin->find_transactions_by_date = &postgres_find_transactions_by_date; plugin->find_payments_by_id = &postgres_find_payments_by_id; + plugin->find_payments_by_id_and_coin = &postgres_find_payments_by_id_and_coin; plugin->find_transfers_by_id = &postgres_find_transfers_by_id; plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid; plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -383,6 +383,7 @@ run (void *cls) FAILIF (GNUNET_OK != plugin->find_payments_by_id (plugin->cls, transaction_id, + &merchant_pub, &deposit_cb, NULL)); FAILIF (GNUNET_OK != diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -64,7 +64,8 @@ typedef void * @param coin_pub public key of the coin * @param amount_with_fee amount the exchange will deposit for this coin * @param deposit_fee fee the exchange will charge for this coin - * @param exchange_proof proof from exchange that coin was accepted + * @param exchange_proof proof from exchange that coin was accepted, + * matches the `interface DepositSuccess` of the documentation. */ typedef void (*TALER_MERCHANTDB_CoinDepositCallback)(void *cls, @@ -257,7 +258,7 @@ struct TALER_MERCHANTDB_Plugin * * @param cls our plugin handle * @param transaction_id the transaction id to search - * @param merchant_pub merchant's public key. It's AND'd with transaction_id + * @param merchant_pub merchant's public key. It's AND'd with @a transaction_id * in order to find the result. * @param cb function to call with transaction data * @param cb_cls closure for @a cb @@ -276,6 +277,8 @@ struct TALER_MERCHANTDB_Plugin * * @param cls closure * @param transaction_id key for the search + * @param merchant_pub merchant's public key. It's AND'd with @a transaction_id + * in order to find the result. * @param cb function to call with payment data * @param cb_cls closure for @a cb * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, @@ -284,11 +287,34 @@ struct TALER_MERCHANTDB_Plugin int (*find_payments_by_id) (void *cls, uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, TALER_MERCHANTDB_CoinDepositCallback cb, void *cb_cls); /** + * Lookup information about coin payments by transaction ID and coin. + * + * @param cls closure + * @param transaction_id key for the search + * @param merchant_pub merchant's public key. It's AND'd with @a transaction_id + * in order to find the result. + * @param coin_pub public key to use for the search + * @param cb function to call with payment data + * @param cb_cls closure for @a cb + * @return #GNUNET_OK on success, #GNUNET_NO if transaction Id is unknown, + * #GNUNET_SYSERR on hard errors + */ + int + (*find_payments_by_id_and_coin) (void *cls, + uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + TALER_MERCHANTDB_CoinDepositCallback cb, + void *cb_cls); + + + /** * Lookup information about a transfer by @a transaction_id. Note * that in theory there could be multiple wire transfers for a * single @a transaction_id, as the transaction may have involved diff --git a/src/lib/merchant_api_track_transfer.c b/src/lib/merchant_api_track_transfer.c @@ -77,13 +77,13 @@ struct TALER_MERCHANT_TrackTransferHandle * Any changes should likely be reflected there as well. * * @param wdh handle to the operation - * @param json response we got as the 'details' from the exchange + * @param json response we got * @return #GNUNET_OK if we are done and all is well, * #GNUNET_SYSERR if the response was bogus */ static int -parse_exchange_details_ok (struct TALER_MERCHANT_TrackTransferHandle *wdh, - const json_t *json) +check_track_transfer_response_ok (struct TALER_MERCHANT_TrackTransferHandle *wdh, + const json_t *json) { json_t *details_j; struct GNUNET_HashCode h_wire; @@ -146,71 +146,12 @@ parse_exchange_details_ok (struct TALER_MERCHANT_TrackTransferHandle *wdh, details); } GNUNET_JSON_parse_free (inner_spec); + TALER_MERCHANT_track_transfer_cancel (wdh); return GNUNET_OK; } /** - * We got a #MHD_HTTP_OK response for the /track/transfer request. - * Check that the response is well-formed and if it is, call the - * callback. If not, return an error code. - * - * This code is very similar to - * exchange_api_track_transfer.c::check_track_transfer_response_ok. - * (Except we do not check the signature, as that was done by the - * backend which we trust already.) - * Any changes should likely be reflected there as well. - * - * @param wdh handle to the operation - * @param json response we got - * @return #GNUNET_OK if we are done and all is well, - * #GNUNET_SYSERR if the response was bogus - */ -static int -check_track_transfer_response_ok (struct TALER_MERCHANT_TrackTransferHandle *wdh, - const json_t *json) -{ - int ret; - json_t *inner_j; - uint32_t exchange_status; - struct GNUNET_JSON_Specification outer_spec[] = { - GNUNET_JSON_spec_json ("details", &inner_j), - GNUNET_JSON_spec_uint32 ("exchange_status", &exchange_status), - GNUNET_JSON_spec_end() - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - outer_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - switch (exchange_status) - { - case MHD_HTTP_OK: - ret = parse_exchange_details_ok (wdh, - inner_j); - break; - /* FIXME: other acceptable exchange status codes to handle here? - FIXME: implement proper way to pass exchange status vs. backend - status code to application! */ - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected exchange status code %u\n", - (unsigned int) exchange_status); - ret = GNUNET_SYSERR; - break; - } - if (GNUNET_OK == ret) - TALER_MERCHANT_track_transfer_cancel (wdh); - GNUNET_JSON_parse_free (outer_spec); - return ret; -} - - -/** * Function called when we're done processing the * HTTP /track/transfer request. *