summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/taler-merchant-httpd_pay.c570
-rw-r--r--src/backend/taler-merchant-httpd_proposal.c197
-rw-r--r--src/backend/taler-merchant-httpd_track-transaction.c13
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c26
-rw-r--r--src/backenddb/test_merchantdb.c4
-rw-r--r--src/include/taler_merchant_service.h13
-rw-r--r--src/include/taler_merchantdb_plugin.h6
-rw-r--r--src/lib/merchant_api_pay.c158
-rw-r--r--src/lib/merchant_api_proposal.c6
-rw-r--r--src/lib/test_merchant_api.c50
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 (&timestamp);
- (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",