diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/taler-merchant-httpd_pay.c | 570 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_proposal.c | 197 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_track-transaction.c | 13 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 26 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 4 | ||||
-rw-r--r-- | src/include/taler_merchant_service.h | 13 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 6 | ||||
-rw-r--r-- | src/lib/merchant_api_pay.c | 158 | ||||
-rw-r--r-- | src/lib/merchant_api_proposal.c | 6 | ||||
-rw-r--r-- | src/lib/test_merchant_api.c | 50 |
10 files changed, 546 insertions, 497 deletions
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c index de8649a3..f3be11ad 100644 --- a/src/backend/taler-merchant-httpd_pay.c +++ b/src/backend/taler-merchant-httpd_pay.c @@ -38,11 +38,6 @@ */ #define PAY_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)) -/** - * The instance which is working this request - */ -struct MerchantInstance *mi; - /** * Information we keep for an individual call to the /pay handler. @@ -247,6 +242,12 @@ struct PayContext */ struct MerchantInstance *mi; + /** + * Proposal data for the proposal that is being + * payed for in this context. + */ + json_t *proposal_data; + }; @@ -327,6 +328,36 @@ abort_deposit (struct PayContext *pc) /** + * Generate a response that indicates payment success. + * + * @param pc payment context + * @return the mhd response + */ +struct MHD_Response * +sign_success_response (struct PayContext *pc) +{ + struct GNUNET_CRYPTO_EddsaSignature sig; + struct PaymentResponsePS mr; + + mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK); + mr.purpose.size = htonl (sizeof (mr)); + mr.h_proposal_data = pc->h_proposal_data; + + GNUNET_CRYPTO_eddsa_sign (&pc->mi->privkey.eddsa_priv, + &mr.purpose, + &sig); + + return TMH_RESPONSE_make_json_pack ("{s:O, s:s, s:o}", + "proposal_data", pc->proposal_data, + "sig", + json_string_value (GNUNET_JSON_from_data_auto (&sig)), + "h_proposal_data", + GNUNET_JSON_from_data (&pc->h_proposal_data, + sizeof (struct GNUNET_HashCode))); +} + + +/** * Callback to handle a deposit permission's response. * * @param cls a `struct DepositConfirmation` (i.e. a pointer @@ -352,8 +383,6 @@ deposit_cb (void *cls, { struct DepositConfirmation *dc = cls; struct PayContext *pc = dc->pc; - struct GNUNET_CRYPTO_EddsaSignature sig; - struct PaymentResponsePS mr; dc->dh = NULL; pc->pending--; @@ -424,21 +453,7 @@ deposit_cb (void *cls, return; /* still more to do */ - mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK); - mr.purpose.size = htonl (sizeof (mr)); - mr.h_proposal_data = pc->h_proposal_data; - - GNUNET_CRYPTO_eddsa_sign (&mi->privkey.eddsa_priv, - &mr.purpose, - &sig); - resume_pay_with_response (pc, - MHD_HTTP_OK, - TMH_RESPONSE_make_json_pack ("{s:s, s:o}", - "sig", - json_string_value (GNUNET_JSON_from_data_auto (&sig)), - "h_proposal_data", - GNUNET_JSON_from_data (&pc->h_proposal_data, - sizeof (struct GNUNET_HashCode)))); + resume_pay_with_response (pc, MHD_HTTP_OK, sign_success_response (pc)); } @@ -496,6 +511,11 @@ pay_context_cleanup (struct TM_HandlerContext *hc) GNUNET_free (pc->chosen_exchange); pc->chosen_exchange = NULL; } + if (NULL != pc->proposal_data) + { + json_decref (pc->proposal_data); + pc->proposal_data = NULL; + } GNUNET_free (pc); } @@ -882,279 +902,243 @@ transaction_double_check (void *cls, const struct TALER_Amount *total_amount) { return; -} +} + + /** - * Accomplish this payment. + * Try to parse the pay request into the given pay context. * - * @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 + * Schedules an error response in the connection on failure. + * + * @return #GNUNET_YES on success */ -int -MH_handler_pay (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size) +static int +parse_pay (struct MHD_Connection *connection, json_t *root, struct PayContext *pc) { - struct PayContext *pc; + json_t *coins; + json_t *coin; + json_t *merchant; + unsigned int coins_index; + const char *chosen_exchange; + const char *order_id; + struct GNUNET_HashCode h_oid; + struct TALER_MerchantPublicKeyP merchant_pub; int res; - json_t *root; - struct GNUNET_TIME_Absolute now; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "In handler for /pay.\n"); - if (NULL == *connection_cls) + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("coins", &coins), + GNUNET_JSON_spec_string ("exchange", &chosen_exchange), + GNUNET_JSON_spec_string ("order_id", &order_id), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub), + GNUNET_JSON_spec_end() + }; + + res = TMH_PARSE_json_data (connection, + root, + spec); + if (GNUNET_YES != res) { - 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; + GNUNET_break (0); + return res; } - if (0 != pc->response_code) + + GNUNET_CRYPTO_hash (order_id, + strlen (order_id), + &h_oid); + + res = db->find_proposal_data (db->cls, + &pc->proposal_data, + &h_oid, + &merchant_pub); + + + if (GNUNET_OK != res) { - /* We are *done* processing the request, just queue the response (!) */ - if (UINT_MAX == pc->response_code) + + if (MHD_YES != TMH_RESPONSE_reply_not_found (connection, + TALER_EC_PAY_DB_STORE_PAY_ERROR, + "Proposal not found")) { GNUNET_break (0); - return MHD_NO; /* hard error */ + return GNUNET_SYSERR; } - res = MHD_queue_response (connection, - pc->response_code, - pc->response); - if (NULL != pc->response) + return GNUNET_NO; + } + + + if (GNUNET_OK != TALER_JSON_hash (pc->proposal_data, &pc->h_proposal_data)) + { + if (MHD_YES != TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_NONE, + "Can not hash proposal")) { - MHD_destroy_response (pc->response); - pc->response = NULL; + GNUNET_break (0); + return GNUNET_SYSERR; } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Queueing response (%u) for /pay (%s).\n", - (unsigned int) pc->response_code, - res ? "OK" : "FAILED"); - return res; + return GNUNET_NO; } - if (NULL != pc->chosen_exchange) + + + merchant = json_object_get (pc->proposal_data, "merchant"); + if (NULL == merchant) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Shouldn't be here. Old MHD version?\n"); - return MHD_YES; + // invalid contract: + GNUNET_break (0); + if (MHD_YES != TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_NONE, + "No merchant field in contract")) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; } - res = TMH_PARSE_post_json (connection, - &pc->json_parse_context, - upload_data, - upload_data_size, - &root); - if (GNUNET_SYSERR == res) + pc->mi = get_instance (merchant); + + if (NULL == pc->mi) { - GNUNET_break (0); - return TMH_RESPONSE_reply_invalid_json (connection); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Not able to find the specified instance\n"); + if (MHD_NO == TMH_RESPONSE_reply_not_found (connection, + TALER_EC_PAY_INSTANCE_UNKNOWN, + "Unknown instance given")) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; } - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; /* the POST's body has to be further fetched */ - mi = get_instance (root); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "/pay: picked instance %s with key %s\n", + pc->mi->id, + GNUNET_STRINGS_data_to_string_alloc (&pc->mi->pubkey, sizeof (pc->mi->pubkey))); + + pc->chosen_exchange = GNUNET_strdup (chosen_exchange); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Parsed JSON for /pay.\n"); + + - /* Got the JSON upload, parse it */ { - json_t *coins; - json_t *coin; - unsigned int coins_index; - struct TALER_MerchantSignatureP merchant_sig; - struct TALER_ProposalDataPS pdps; - 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_proposal_data", &pc->h_proposal_data), - 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 ("pay_deadline", &pc->pay_deadline), - GNUNET_JSON_spec_absolute_time ("timestamp", &pc->timestamp), + struct GNUNET_JSON_Specification espec[] = { + GNUNET_JSON_spec_absolute_time ("refund_deadline", + &pc->refund_deadline), + GNUNET_JSON_spec_absolute_time ("pay_deadline", + &pc->pay_deadline), + GNUNET_JSON_spec_absolute_time ("timestamp", + &pc->timestamp), + TALER_JSON_spec_amount ("max_fee", + &pc->max_fee), + TALER_JSON_spec_amount ("amount", + &pc->amount), GNUNET_JSON_spec_end() }; res = TMH_PARSE_json_data (connection, - root, - spec); + pc->proposal_data, + espec); if (GNUNET_YES != res) { - json_decref (root); + GNUNET_JSON_parse_free (spec); 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 instance\n"); - json_decref (root); - return TMH_RESPONSE_reply_not_found (connection, - TALER_EC_PAY_INSTANCE_UNKNOWN, - "Unknown instance given"); - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "The instance 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"); - pdps.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); - pdps.purpose.size = htonl (sizeof (pdps)); - pdps.hash = pc->h_proposal_data; - - struct GNUNET_HashCode dummy; - GNUNET_CRYPTO_hash (&merchant_sig.eddsa_sig, - sizeof (merchant_sig.eddsa_sig), - &dummy); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Verifying signature '%s'\n", - GNUNET_h2s (&dummy)); + pc->wire_transfer_deadline = GNUNET_TIME_absolute_add (pc->timestamp, wire_transfer_delay); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "of hashed data '%s'\n", - GNUNET_h2s (&pc->h_proposal_data)); - - GNUNET_CRYPTO_hash (&pc->mi->privkey.eddsa_priv, - sizeof (pc->mi->privkey.eddsa_priv), - &dummy); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "with private key '%s'\n", - GNUNET_h2s (&dummy)); - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_CONTRACT, - &pdps.purpose, - &merchant_sig.eddsa_sig, - &pc->mi->pubkey.eddsa_pub)) + 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, - TALER_EC_PAY_MERCHANT_SIGNATURE_INVALID, - "invalid merchant signature supplied"); + TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE, + "refund deadline after wire transfer deadline"); } + } - /* '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, - TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE, - "refund deadline after wire transfer deadline"); - } - } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "parsed timestamps\n"); - pc->coins_cnt = json_array_size (coins); - if (0 == pc->coins_cnt) + pc->coins_cnt = json_array_size (coins); + if (0 == pc->coins_cnt) + { + GNUNET_JSON_parse_free (spec); + return TMH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_PAY_COINS_ARRAY_EMPTY, + "coins"); + } + /* 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); - return TMH_RESPONSE_reply_arg_invalid (connection, - TALER_EC_PAY_COINS_ARRAY_EMPTY, - "coins"); + GNUNET_break (0); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } - /* 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_log (GNUNET_ERROR_TYPE_INFO, "parsed coins\n"); - { - 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; + GNUNET_JSON_parse_free (spec); + + return GNUNET_OK; +} + + +/** + * Process a payment for a proposal. + */ +static int +handler_pay_json (struct MHD_Connection *connection, + json_t *root, + struct PayContext *pc) +{ + int ret; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "about to parse '/pay' body\n"); + + ret = parse_pay (connection, root, pc); + if (GNUNET_OK != ret) + return ret; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "parsed '/pay' body\n"); + /* Check if this payment attempt has already succeeded */ if (GNUNET_SYSERR == db->find_payments (db->cls, @@ -1164,7 +1148,6 @@ MH_handler_pay (struct TMH_RequestHandler *rh, pc)) { GNUNET_break (0); - json_decref (root); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, "Merchant database error"); @@ -1181,9 +1164,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh, MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response (connection, MHD_HTTP_OK, - resp); + sign_success_response (pc)); MHD_destroy_response (resp); - json_decref (root); return ret; } /* Check if transaction is already known, if not store it. */ @@ -1195,7 +1177,6 @@ MH_handler_pay (struct TMH_RequestHandler *rh, pc)) { GNUNET_break (0); - json_decref (root); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, "Merchant database error"); @@ -1203,16 +1184,17 @@ MH_handler_pay (struct TMH_RequestHandler *rh, if (GNUNET_SYSERR == pc->transaction_exits) { GNUNET_break (0); - json_decref (root); return TMH_RESPONSE_reply_external_error (connection, TALER_EC_PAY_DB_TRANSACTION_ID_CONFLICT, "Transaction ID reused with different transaction details"); } if (GNUNET_NO == pc->transaction_exits) { + struct GNUNET_TIME_Absolute now; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Dealing with new transaction '%s'\n", GNUNET_h2s (&pc->h_proposal_data)); + now = GNUNET_TIME_absolute_get (); if (now.abs_value_us > pc->pay_deadline.abs_value_us) { @@ -1243,7 +1225,6 @@ MH_handler_pay (struct TMH_RequestHandler *rh, &pc->amount)) { GNUNET_break (0); - json_decref (root); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR, "Merchant database error"); @@ -1272,7 +1253,94 @@ MH_handler_pay (struct TMH_RequestHandler *rh, pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT, &handle_pay_timeout, pc); + return GNUNET_OK; +} + + +/** + * Process a payment for a proposal. + * Takes data from the given MHD connection. + * + * @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) + { + // FIXME: explain in comment why this could happen! + 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_invalid_json (connection); + } + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; /* the POST's body has to be further fetched */ + + res = handler_pay_json (connection, root, pc); json_decref (root); + if (GNUNET_SYSERR == res) + return MHD_NO; return MHD_YES; } diff --git a/src/backend/taler-merchant-httpd_proposal.c b/src/backend/taler-merchant-httpd_proposal.c index 6e45b2db..51ea0389 100644 --- a/src/backend/taler-merchant-httpd_proposal.c +++ b/src/backend/taler-merchant-httpd_proposal.c @@ -120,30 +120,18 @@ get_instance (struct json_t *json); /** - * Generate a proposal, given its order. In practical terms, it adds the - * fields 'exchanges', 'merchant_pub', and 'H_wire' to the order gotten - * from the frontend. Finally, it signs this data, and returns it to the - * frontend. + * Transform an order into a proposal and store it in the database. + * Write the resulting proposal or an error message ot a MHD connection * - * @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 + * @param connection connection to write the result or error to + * @param order to process * @return MHD result code */ int -MH_handler_proposal_put (struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size) +proposal_put (struct MHD_Connection *connection, json_t *order) { - - json_t *root; - json_t *order; int res; struct MerchantInstance *mi; - struct TMH_JsonParseContext *ctx; struct TALER_ProposalDataPS pdps; struct GNUNET_CRYPTO_EddsaSignature merchant_sig; struct TALER_Amount total; @@ -157,8 +145,8 @@ MH_handler_proposal_put (struct TMH_RequestHandler *rh, struct GNUNET_TIME_Absolute pay_deadline; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount ("amount", &total), - TALER_JSON_spec_amount ("max_fee", &max_fee), GNUNET_JSON_spec_string ("order_id", &order_id), + TALER_JSON_spec_amount ("max_fee", &max_fee), /* The following entries we don't actually need, except to check that the order is well-formed */ GNUNET_JSON_spec_json ("products", &products), @@ -169,58 +157,66 @@ MH_handler_proposal_put (struct TMH_RequestHandler *rh, GNUNET_JSON_spec_end () }; - if (NULL == *connection_cls) + + /* Add order_id if it doesn't exist. */ + + if (NULL == json_string_value (json_object_get (order, "order_id"))) { - ctx = GNUNET_new (struct TMH_JsonParseContext); - ctx->hc.cc = &json_parse_cleanup; - *connection_cls = ctx; + char buf[256]; + time_t timer; + struct tm* tm_info; + size_t off; + + time (&timer); + tm_info = localtime (&timer); + + off = strftime (buf, sizeof (buf), "%H:%M:%S", tm_info); + snprintf (buf + off, sizeof (buf) - off, + "-%llX", + (long long unsigned) GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX)); + json_object_set (order, "order_id", json_string (buf)); } - else + + if (NULL == json_string_value (json_object_get (order, "timestamp"))) { - ctx = *connection_cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + (void) GNUNET_TIME_round_abs (&now); + json_object_set (order, "timestamp", GNUNET_JSON_from_time_abs (now)); } - res = TMH_PARSE_post_json (connection, - &ctx->json_parse_context, - upload_data, - upload_data_size, - &root); - if (GNUNET_SYSERR == res) - return MHD_NO; - /* the POST's body has to be further fetched */ - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; + if (NULL == json_string_value (json_object_get (order, "refund_deadline"))) + { + struct GNUNET_TIME_Absolute zero = { 0 }; + json_object_set (order, "refund_deadline", GNUNET_JSON_from_time_abs (zero)); + } - order = json_object_get (root, - "order"); - if (NULL == order) + if (NULL == json_string_value (json_object_get (order, "pay_deadline"))) { - json_decref (root); - return TMH_RESPONSE_reply_arg_missing (connection, - TALER_EC_PARAMETER_MISSING, - "order"); + struct GNUNET_TIME_Absolute t; + /* FIXME: read the delay for pay_deadline from config */ + t = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_HOURS); + (void) GNUNET_TIME_round_abs (&t); + json_object_set (order, "pay_deadline", GNUNET_JSON_from_time_abs (t)); } + /* extract fields we need to sign separately */ - res = TMH_PARSE_json_data (connection, - order, - spec); + res = TMH_PARSE_json_data (connection, order, spec); if (GNUNET_NO == res) { - json_decref (root); return MHD_YES; } if (GNUNET_SYSERR == res) { - json_decref (root); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_NONE, "Impossible to parse the order"); } + + /* check contract is well-formed */ if (GNUNET_OK != check_products (products)) { GNUNET_JSON_parse_free (spec); - json_decref (root); return TMH_RESPONSE_reply_arg_invalid (connection, TALER_EC_PARAMETER_MALFORMED, "order:products"); @@ -231,7 +227,6 @@ MH_handler_proposal_put (struct TMH_RequestHandler *rh, { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Not able to find the specified instance\n"); - json_decref (root); return TMH_RESPONSE_reply_not_found (connection, TALER_EC_CONTRACT_INSTANCE_UNKNOWN, "Unknown instance given"); @@ -260,42 +255,26 @@ MH_handler_proposal_put (struct TMH_RequestHandler *rh, TALER_JSON_hash (order, &pdps.hash)); - /*FIXME: do NOT keep in production, private key logged!*/ - struct GNUNET_HashCode dummy; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Signing h_proposal_data '%s'\n", - GNUNET_h2s (&pdps.hash)); - - GNUNET_CRYPTO_hash (&mi->privkey.eddsa_priv, - sizeof (mi->privkey.eddsa_priv), - &dummy); - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "with private key '%s'\n", - GNUNET_h2s (&dummy)); - GNUNET_CRYPTO_eddsa_sign (&mi->privkey.eddsa_priv, &pdps.purpose, &merchant_sig); - GNUNET_CRYPTO_hash (&merchant_sig, - sizeof (merchant_sig), - &dummy); - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "generating signature '%s'\n", - GNUNET_h2s (&dummy)); - GNUNET_CRYPTO_hash (order_id, strlen (order_id), &h_oid); + + if (GNUNET_OK != db->insert_proposal_data (db->cls, &h_oid, + &mi->pubkey, order)) + { + GNUNET_JSON_parse_free (spec); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_PROPOSAL_STORE_DB_ERROR, "db error: could not store this proposal's data into db"); + } res = TMH_RESPONSE_reply_json_pack (connection, @@ -305,6 +284,68 @@ MH_handler_proposal_put (struct TMH_RequestHandler *rh, "sig", GNUNET_JSON_from_data_auto (&merchant_sig), "hash", GNUNET_JSON_from_data_auto (&pdps.hash)); GNUNET_JSON_parse_free (spec); + return res; +} + + +/** + * Generate a proposal, given its order. In practical terms, it adds the + * fields 'exchanges', 'merchant_pub', and 'H_wire' to the order gotten + * from the frontend. Finally, it signs this data, and returns it to the + * frontend. + * + * @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_proposal_put (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + int res; + struct TMH_JsonParseContext *ctx; + + if (NULL == *connection_cls) + { + ctx = GNUNET_new (struct TMH_JsonParseContext); + ctx->hc.cc = &json_parse_cleanup; + *connection_cls = ctx; + } + else + { + ctx = *connection_cls; + } + + json_t *root; + + res = TMH_PARSE_post_json (connection, + &ctx->json_parse_context, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + /* the POST's body has to be further fetched */ + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + + json_t *order = json_object_get (root, "order"); + + if (NULL == order) + { + res = TMH_RESPONSE_reply_arg_missing (connection, + TALER_EC_PARAMETER_MISSING, + "order"); + } + else + { + res = proposal_put (connection, order); + } json_decref (root); return res; } @@ -329,9 +370,22 @@ MH_handler_proposal_lookup (struct TMH_RequestHandler *rh, size_t *upload_data_size) { const char *order_id; + const char *instance; struct GNUNET_HashCode h_oid; int res; json_t *proposal_data; + struct MerchantInstance *mi; + + instance = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "instance"); + if (NULL == instance) + return TMH_RESPONSE_reply_arg_missing (connection, + TALER_EC_PARAMETER_MISSING, + "instance"); + + mi = TMH_lookup_instance (instance); + GNUNET_assert (NULL != mi); order_id = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, @@ -346,7 +400,8 @@ MH_handler_proposal_lookup (struct TMH_RequestHandler *rh, res = db->find_proposal_data (db->cls, &proposal_data, - &h_oid); + &h_oid, + &mi->pubkey); if (GNUNET_NO == res) return TMH_RESPONSE_reply_not_found (connection, TALER_EC_PROPOSAL_LOOKUP_NOT_FOUND, diff --git a/src/backend/taler-merchant-httpd_track-transaction.c b/src/backend/taler-merchant-httpd_track-transaction.c index 0e48b3c6..608c3eb9 100644 --- a/src/backend/taler-merchant-httpd_track-transaction.c +++ b/src/backend/taler-merchant-httpd_track-transaction.c @@ -907,17 +907,21 @@ MH_handler_track_transaction (struct TMH_RequestHandler *rh, "instance"); if (NULL == instance) instance = "default"; + GNUNET_CRYPTO_hash (instance, strlen (instance), &h_instance); + GNUNET_CRYPTO_hash (order_id, strlen (order_id), &h_order_id); - - tctx->mi = GNUNET_CONTAINER_multihashmap_get (by_id_map, &h_instance); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Tracking on behalf of instance '%s'\n", + instance); + if (NULL == tctx->mi) return TMH_RESPONSE_reply_not_found (connection, TALER_EC_TRACK_TRANSACTION_INSTANCE_UNKNOWN, @@ -925,7 +929,8 @@ MH_handler_track_transaction (struct TMH_RequestHandler *rh, if (GNUNET_YES != db->find_proposal_data (db->cls, &proposal_data, - &h_order_id)) + &h_order_id, + &tctx->mi->pubkey)) return TMH_RESPONSE_reply_not_found (connection, TALER_EC_PROPOSAL_LOOKUP_NOT_FOUND, @@ -944,6 +949,8 @@ MH_handler_track_transaction (struct TMH_RequestHandler *rh, tctx); if (GNUNET_NO == ret) { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "h_proposal_data not found\n"); return TMH_RESPONSE_reply_not_found (connection, TALER_EC_TRACK_TRANSACTION_TRANSACTION_UNKNOWN, "h_proposal_data is unknown"); diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 6a8bb46b..28459248 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -165,8 +165,9 @@ postgres_initialize (void *cls) PG_EXEC (pg, "CREATE TABLE IF NOT EXISTS merchant_proposal_data (" "h_order_id BYTEA NOT NULL" + ",merchant_pub BYTEA NOT NULL" ",proposal_data BYTEA NOT NULL" - ",PRIMARY KEY (h_order_id)" + ",PRIMARY KEY (h_order_id, merchant_pub)" ");"); PG_EXEC (pg, @@ -279,17 +280,19 @@ postgres_initialize (void *cls) "insert_proposal_data", "INSERT INTO merchant_proposal_data" "(h_order_id" + ",merchant_pub" ",proposal_data)" " VALUES " - "($1, $2)", - 2); + "($1, $2, $3)", + 3); PG_PREPARE (pg, "find_proposal_data", "SELECT proposal_data FROM merchant_proposal_data" " WHERE" - " h_order_id=$1", - 1); + " h_order_id=$1" + " AND merchant_pub=$2", + 2); PG_PREPARE (pg, "find_transactions_by_date", @@ -406,7 +409,8 @@ postgres_initialize (void *cls) static int postgres_find_proposal_data (void *cls, json_t **proposal_data, - struct GNUNET_HashCode *h_transaction_id) + const struct GNUNET_HashCode *h_transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub) { struct PostgresClosure *pg = cls; PGresult *result; @@ -414,6 +418,7 @@ postgres_find_proposal_data (void *cls, struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_transaction_id), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), GNUNET_PQ_query_param_end }; @@ -461,7 +466,8 @@ postgres_find_proposal_data (void *cls, */ static int postgres_insert_proposal_data (void *cls, - struct GNUNET_HashCode *h_transaction_id, + const struct GNUNET_HashCode *h_transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, const json_t *proposal_data) { struct PostgresClosure *pg = cls; @@ -470,6 +476,7 @@ postgres_insert_proposal_data (void *cls, struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_transaction_id), + GNUNET_PQ_query_param_auto_from_type (merchant_pub), TALER_PQ_query_param_json (proposal_data), GNUNET_PQ_query_param_end }; @@ -1356,6 +1363,11 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) } } pg->conn = GNUNET_POSTGRES_connect (cfg, "merchantdb-postgres"); + if (NULL == pg->conn) + { + GNUNET_break (0); + return NULL; + } plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin); plugin->cls = pg; plugin->drop_tables = &postgres_drop_tables; diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index b74549d4..c8366f34 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -372,6 +372,7 @@ run (void *cls) FAILIF (GNUNET_OK != plugin->insert_proposal_data (plugin->cls, &h_transaction_id, + &merchant_pub, proposal_data)); json_t *out; @@ -379,7 +380,8 @@ run (void *cls) FAILIF (GNUNET_OK != plugin->find_proposal_data (plugin->cls, &out, // plain data - &h_transaction_id)); + &h_transaction_id, + &merchant_pub)); FAILIF (GNUNET_OK != plugin->store_transaction (plugin->cls, diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h index 61919d09..99bab17b 100644 --- a/src/include/taler_merchant_service.h +++ b/src/include/taler_merchant_service.h @@ -123,6 +123,7 @@ struct TALER_MERCHANT_ProposalLookupOperation * TALER_MERCHANT_proposal_lookup (struct GNUNET_CURL_Context *ctx, const char *backend_uri, const char *transaction_id, + const char *instance, TALER_MERCHANT_ProposalLookupOperationCallback plo_cb, void *plo_cb_cls); @@ -247,6 +248,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, struct GNUNET_TIME_Absolute pay_deadline, const struct GNUNET_HashCode *h_wire, const char *exchange_uri, + const char *order_id, unsigned int num_coins, const struct TALER_MERCHANT_PayCoin *coins, TALER_MERCHANT_PayCallback pay_cb, @@ -326,15 +328,8 @@ struct TALER_MERCHANT_PaidCoin struct TALER_MERCHANT_Pay * TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, const char *merchant_uri, - const char *instance, - const struct GNUNET_HashCode *h_contract, - const struct TALER_Amount *amount, - const struct TALER_Amount *max_fee, - const struct TALER_MerchantSignatureP *merchant_sig, - struct GNUNET_TIME_Absolute refund_deadline, - struct GNUNET_TIME_Absolute pay_deadline, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute wire_transfer_deadline, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *order_id, const char *exchange_uri, unsigned int num_coins, const struct TALER_MERCHANT_PaidCoin *coins, diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 06f1c565..33102a03 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -158,7 +158,8 @@ struct TALER_MERCHANTDB_Plugin */ int (*insert_proposal_data) (void *cls, - struct GNUNET_HashCode *h_transaction_id, + const struct GNUNET_HashCode *h_transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, const json_t *proposal_data); @@ -176,7 +177,8 @@ struct TALER_MERCHANTDB_Plugin int (*find_proposal_data) (void *cls, json_t **proposal_data, - struct GNUNET_HashCode *h_transaction_id); + const struct GNUNET_HashCode *h_transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub); /** diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c index 76c247ab..6910bdec 100644 --- a/src/lib/merchant_api_pay.c +++ b/src/lib/merchant_api_pay.c @@ -288,6 +288,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, struct GNUNET_TIME_Absolute pay_deadline, const struct GNUNET_HashCode *h_wire, const char *exchange_uri, + const char *order_id, unsigned int num_coins, const struct TALER_MERCHANT_PayCoin *coins, TALER_MERCHANT_PayCallback pay_cb, @@ -301,6 +302,14 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, (void) GNUNET_TIME_round_abs (&pay_deadline); (void) GNUNET_TIME_round_abs (&refund_deadline); + if (GNUNET_YES != + TALER_amount_cmp_currency (amount, + max_fee)) + { + GNUNET_break (0); + return NULL; + } + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); dr.h_proposal_data = *h_proposal_data; @@ -331,6 +340,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, } TALER_amount_hton (&dr.deposit_fee, &fee); + GNUNET_CRYPTO_eddsa_sign (&coin->coin_priv.eddsa_priv, &dr.purpose, &p->coin_sig.eddsa_signature); @@ -343,15 +353,8 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, } return TALER_MERCHANT_pay_frontend (ctx, merchant_uri, - instance, - h_proposal_data, - amount, - max_fee, - merchant_sig, - refund_deadline, - pay_deadline, - timestamp, - GNUNET_TIME_UNIT_ZERO_ABS, + merchant_pub, + order_id, exchange_uri, num_coins, pc, @@ -368,19 +371,9 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, * * @param ctx the execution loop context * @param merchant_uri base URI of the merchant's backend - * @param instance which merchant instance will receive this payment - * @param h_contract hash of the contact of the merchant with the customer - * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant - * @param transaction_id transaction id for the transaction between merchant and customer - * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) - * @param deadline to pay for this contract - * @param wire_transfer_deadline date by which the merchant would like the exchange to execute the wire transfer (can be zero if there is no specific date desired by the frontend). If non-zero, must be larger than @a refund_deadline. * @param exchange_uri URI of the exchange that the coins belong to * @param num_coins number of coins used to pay * @param coins array of coins we use to pay - * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. - * @param max_fee maximum fee covered by the merchant (according to the contract) - * @param amount total value of the contract to be paid to the merchant * @param pay_cb the callback to call when a reply for this request is available * @param pay_cb_cls closure for @a pay_cb * @return a handle for this request @@ -388,15 +381,8 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx, struct TALER_MERCHANT_Pay * TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, const char *merchant_uri, - const char *instance, - const struct GNUNET_HashCode *h_proposal_data, - const struct TALER_Amount *amount, - const struct TALER_Amount *max_fee, - const struct TALER_MerchantSignatureP *merchant_sig, - struct GNUNET_TIME_Absolute refund_deadline, - struct GNUNET_TIME_Absolute pay_deadline, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute wire_transfer_deadline, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *order_id, const char *exchange_uri, unsigned int num_coins, const struct TALER_MERCHANT_PaidCoin *coins, @@ -411,23 +397,6 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, struct TALER_Amount total_amount; unsigned int i; - (void) GNUNET_TIME_round_abs (×tamp); - (void) GNUNET_TIME_round_abs (&refund_deadline); - (void) GNUNET_TIME_round_abs (&wire_transfer_deadline); - - if (GNUNET_YES != - TALER_amount_cmp_currency (amount, - max_fee)) - { - GNUNET_break (0); - return NULL; - } - if ( (0 != wire_transfer_deadline.abs_value_us) && - (wire_transfer_deadline.abs_value_us < refund_deadline.abs_value_us) ) - { - GNUNET_break (0); - return NULL; - } if (0 == num_coins) { GNUNET_break (0); @@ -489,101 +458,16 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx, j_coin)); } - { /* Sanity check that total_amount and total_fee - match amount/max_fee requirements */ - struct TALER_Amount fee_left; - - if (GNUNET_OK == - TALER_amount_subtract (&fee_left, - &total_fee, - max_fee)) - { - /* Wallet must cover part of the fee! */ - struct TALER_Amount new_amount; - - if (GNUNET_OK != - TALER_amount_add (&new_amount, - &fee_left, - amount)) - { - /* integer overflow */ - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - if (GNUNET_YES != - TALER_amount_cmp_currency (&new_amount, - &total_amount)) - { - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - if (1 == - TALER_amount_cmp (&new_amount, - &total_amount)) - { - /* new_amount > total_amount: all of the coins (total_amount) - do not add up to at least the new_amount owed to the - merchant, this request is bogus, abort */ - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - } - else - { - /* Full fee covered by merchant, but our total - must at least cover the total contract amount */ - if (GNUNET_YES != - TALER_amount_cmp_currency (amount, - &total_amount)) - { - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - if (1 == - TALER_amount_cmp (amount, - &total_amount)) - { - /* amount > total_amount: all of the coins (total_amount) do - not add up to at least the amount owed to the merchant, - this request is bogus, abort */ - GNUNET_break (0); - json_decref (j_coins); - return NULL; - } - } - } /* end of sanity check */ - - pay_obj = json_pack ("{s:o," /* h_proposal_data */ - " s:o," /* timestamp */ - " s:o, s:o," /* refund_deadline, pay_deadline */ + pay_obj = json_pack ("{" " s:s," /* exchange */ - " s:o, s:o," /* coins, max_fee */ - " s:o, s:o}",/* amount, signature */ - "h_proposal_data", GNUNET_JSON_from_data_auto (h_proposal_data), - "timestamp", GNUNET_JSON_from_time_abs (timestamp), - "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline), - "pay_deadline", GNUNET_JSON_from_time_abs (pay_deadline), + " s:o," /* coins */ + " s:s," /* order_id */ + " s:o," /* merchant_pub */ + "}", "exchange", exchange_uri, "coins", j_coins, - "max_fee", TALER_JSON_from_amount (max_fee), - "amount", TALER_JSON_from_amount (amount), - "merchant_sig", GNUNET_JSON_from_data_auto (merchant_sig)); - if (NULL != instance) - json_object_set_new (pay_obj, - "instance", - json_string (instance)); - - if (0 != wire_transfer_deadline.abs_value_us) - { - /* Frontend did have an execution date in mind, add it */ - json_object_set_new (pay_obj, - "wire_transfer_deadline", - GNUNET_JSON_from_time_abs (wire_transfer_deadline)); - } + "order_id", order_id, + "merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub)); ph = GNUNET_new (struct TALER_MERCHANT_Pay); ph->ctx = ctx; diff --git a/src/lib/merchant_api_proposal.c b/src/lib/merchant_api_proposal.c index 60b38d93..c3e58735 100644 --- a/src/lib/merchant_api_proposal.c +++ b/src/lib/merchant_api_proposal.c @@ -288,6 +288,7 @@ struct TALER_MERCHANT_ProposalLookupOperation * TALER_MERCHANT_proposal_lookup (struct GNUNET_CURL_Context *ctx, const char *backend_uri, const char *order_id, + const char *instance, TALER_MERCHANT_ProposalLookupOperationCallback plo_cb, void *plo_cb_cls) { @@ -300,9 +301,10 @@ TALER_MERCHANT_proposal_lookup (struct GNUNET_CURL_Context *ctx, plo->cb_cls = plo_cb_cls; GNUNET_asprintf (&plo->url, - "%s/proposal?order_id=%s", + "%s/proposal?order_id=%s&instance=%s", backend_uri, - order_id); + order_id, + instance); eh = curl_easy_init (); if (CURLE_OK != curl_easy_setopt (eh, CURLOPT_URL, diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c index a69e4c9a..f0fca8ea 100644 --- a/src/lib/test_merchant_api.c +++ b/src/lib/test_merchant_api.c @@ -1046,6 +1046,9 @@ proposal_cb (void *cls, cmd->details.proposal.proposal_data = json_incref ((json_t *) proposal_data); cmd->details.proposal.merchant_sig = *sig; cmd->details.proposal.hash = *hash; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Hashed proposal, '%s'\n", + GNUNET_h2s (hash)); break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -1357,6 +1360,8 @@ track_transaction_cb (void *cls, struct TALER_Amount ea; struct TALER_Amount coin_contribution; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Successful /track/tracking\n"); if (1 != num_transfers) { GNUNET_break (0); @@ -1511,7 +1516,7 @@ interpreter_run (void *cls) is->ip = 0; instance_idx++; instance = instances[instance_idx]; - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Switching instance: '%s'\n", instance); @@ -1535,6 +1540,7 @@ interpreter_run (void *cls) = TALER_MERCHANT_proposal_lookup (ctx, MERCHANT_URI, order_id, + instance, proposal_lookup_cb, is))); } @@ -1762,9 +1768,6 @@ interpreter_run (void *cls) cmd->details.pay.contract_ref); GNUNET_assert (NULL != ref); merchant_sig = ref->details.proposal.merchant_sig; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Depositing I on '%s'\n", - GNUNET_h2s (&ref->details.proposal.hash)); GNUNET_assert (NULL != ref->details.proposal.proposal_data); { /* Get information that need to be replied in the deposit permission */ @@ -1840,10 +1843,6 @@ interpreter_run (void *cls) } } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Depositing II on '%s'\n", - GNUNET_h2s (&ref->details.proposal.hash)); - cmd->details.pay.ph = TALER_MERCHANT_pay_wallet (ctx, MERCHANT_URI, @@ -1858,6 +1857,7 @@ interpreter_run (void *cls) pay_deadline, &h_wire, EXCHANGE_URI, + order_id, 1 /* num_coins */, &pc /* coins */, &pay_cb, @@ -2294,6 +2294,7 @@ run (void *cls) \"summary\": \"merchant-lib testcase\",\ \"products\":\ [ {\"description\":\"ice cream\", \"value\":\"{EUR:5}\"} ] }"}, + { .oc = OC_PAY, .label = "deposit-simple", .expected_response_code = MHD_HTTP_OK, @@ -2302,6 +2303,15 @@ run (void *cls) .details.pay.amount_with_fee = "EUR:5", .details.pay.amount_without_fee = "EUR:4.99" }, + /* Try to replay payment reusing coin */ + { .oc = OC_PAY, + .label = "replay-simple", + .expected_response_code = MHD_HTTP_OK, + .details.pay.contract_ref = "create-proposal-1", + .details.pay.coin_ref = "withdraw-coin-1", + .details.pay.amount_with_fee = "EUR:5", + .details.pay.amount_without_fee = "EUR:4.99" }, + /* Create another contract */ { .oc = OC_PROPOSAL, @@ -2352,7 +2362,7 @@ run (void *cls) .details.reserve_withdraw.reserve_reference = "create-reserve-2", .details.reserve_withdraw.amount = "EUR:5" }, - /* Fetch contract-1 */ + /* Proposal lookup */ { .oc = OC_PROPOSAL_LOOKUP, .label = "fetch-proposal-2", @@ -2380,6 +2390,13 @@ run (void *cls) { .oc = OC_CHECK_BANK_TRANSFERS_EMPTY, .label = "check_bank_empty" }, + { .oc = OC_TRACK_TRANSACTION, + .label = "track-transaction-1", + .expected_response_code = MHD_HTTP_OK, + .details.track_transaction.expected_transfer_ref = "check_bank_transfer-499c", + .details.track_transaction.pay_ref = "deposit-simple" + }, + /* Trace the WTID back to the original transaction */ { .oc = OC_TRACK_TRANSFER, .label = "track-transfer-1", @@ -2432,10 +2449,14 @@ run (void *cls) .details.track_transfer.check_bank_ref = "check_bank_transfer-499c-2", .details.track_transfer.expected_pay_ref = "deposit-simple-2" }, - /** - * NOTE: could NOT initialize timestamps by calling GNUNET_TIME_xy () - * because that raised a 'Initializer element is not constant' when compiling. - */ + + { .oc = OC_TRACK_TRANSACTION, + .label = "track-transaction-2", + .expected_response_code = MHD_HTTP_OK, + .details.track_transaction.expected_transfer_ref = "check_bank_transfer-499c-2", + .details.track_transaction.pay_ref = "deposit-simple-2" + }, + { .oc = OC_HISTORY, .label = "history-1", .expected_response_code = MHD_HTTP_OK, @@ -2604,7 +2625,8 @@ main (int argc, merchantd = GNUNET_OS_start_process (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL, NULL, NULL, - "taler-merchant-httpd", + "valgrind", + "valgrind", "taler-merchant-httpd", "-c", "test_merchant_api.conf", "-L", "DEBUG", |