summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/taler-merchant-httpd_pay.c1251
-rw-r--r--src/backend/taler-merchant-httpd_responses.c33
-rw-r--r--src/backend/taler-merchant-httpd_responses.h34
-rw-r--r--src/backend/taler-merchant-httpd_track-transaction.c6
-rw-r--r--src/backend/taler-merchant-httpd_track-transfer.c10
-rw-r--r--src/include/taler_merchant_service.h68
-rw-r--r--src/lib/merchant_api_pay.c20
-rw-r--r--src/lib/test_merchant_api.c3
-rw-r--r--src/merchant-tools/taler-merchant-generate-payments.c2
9 files changed, 859 insertions, 568 deletions
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c
index 1315b9c5..8ae6f7d5 100644
--- a/src/backend/taler-merchant-httpd_pay.c
+++ b/src/backend/taler-merchant-httpd_pay.c
@@ -40,9 +40,9 @@
#define PAY_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30))
/**
- * How often do we retry the simple INSERT database transaction?
+ * How often do we retry the (complex!) database transaction?
*/
-#define MAX_RETRIES 3
+#define MAX_RETRIES 5
/**
* Information we keep for an individual call to the /pay handler.
@@ -67,6 +67,11 @@ struct DepositConfirmation
struct TALER_EXCHANGE_DepositHandle *dh;
/**
+ * URL of the exchange that issued this coin.
+ */
+ char *exchange_url;
+
+ /**
* Denomination of this coin.
*/
struct TALER_DenominationPublicKey denom;
@@ -88,6 +93,11 @@ struct DepositConfirmation
struct TALER_Amount refund_fee;
/**
+ * Wire fee charged by the exchange of this coin.
+ */
+ struct TALER_Amount wire_fee;
+
+ /**
* Public key of the coin.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
@@ -185,14 +195,14 @@ struct PayContext
struct TMH_EXCHANGES_FindOperation *fo;
/**
- * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
+ * URL of the exchange used for the last @e fo.
*/
- void *json_parse_context;
-
+ const char *current_exchange;
+
/**
- * Exchange URI given in @e root.
+ * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
*/
- char *chosen_exchange;
+ void *json_parse_context;
/**
* Transaction ID given in @e root.
@@ -211,6 +221,16 @@ struct PayContext
struct GNUNET_HashCode h_wire;
/**
+ * Total wire fees charged by all exchanges involved. Note: there
+ * is a sublte issue with this value not being correctly calculated
+ * if /pay is called a second time, as then some deposits that are
+ * already in the DB are no longer mapped to an exchange (and thus
+ * no fee is looked up). Fixing this would be rather complicated,
+ * and is likely simply no worth it.
+ */
+ struct TALER_Amount total_wire_fee;
+
+ /**
* Maximum fee the merchant is willing to pay, from @e root.
* Note that IF the total fee of the exchange is higher, that is
* acceptable to the merchant if the customer is willing to
@@ -244,6 +264,26 @@ struct PayContext
struct TALER_Amount amount;
/**
+ * Considering all the coins with the "found_in_db" flag
+ * set, what is the total amount we were so far paid on
+ * this contract?
+ */
+ struct TALER_Amount total_paid;
+
+ /**
+ * Considering all the coins with the "found_in_db" flag
+ * set, what is the total amount we had to pay in deposit
+ * fees so far on this contract?
+ */
+ struct TALER_Amount total_fees_paid;
+
+ /**
+ * Considering all the coins with the "found_in_db" flag
+ * set, what is the total amount we already refunded?
+ */
+ struct TALER_Amount total_refunded;
+
+ /**
* Wire transfer deadline. How soon would the merchant like the
* wire transfer to be executed? (Can be given by the frontend
* or be determined by our configuration via #wire_transfer_delay.)
@@ -281,6 +321,11 @@ struct PayContext
unsigned int coins_cnt;
/**
+ * How often have we retried the 'main' transaction?
+ */
+ unsigned int retry_counter;
+
+ /**
* Number of transactions still pending. Initially set to
* @e coins_cnt, decremented on each transaction that
* successfully finished.
@@ -288,6 +333,14 @@ struct PayContext
unsigned int pending;
/**
+ * Number of transactions still pending for the currently selected
+ * exchange. Initially set to the number of coins started at the
+ * exchange, decremented on each transaction that successfully
+ * finished. Once it hits zero, we pick the next exchange.
+ */
+ unsigned int pending_at_ce;
+
+ /**
* HTTP status code to use for the reply, i.e 200 for "OK".
* Special value UINT_MAX is used to indicate hard errors
* (no reply, return #MHD_NO).
@@ -295,14 +348,6 @@ struct PayContext
unsigned int response_code;
/**
- * #GNUNET_NO if the transaction is not in our database,
- * #GNUNET_YES if the transaction is known to our database,
- * #GNUNET_SYSERR if the transaction ID is used for a different
- * transaction in our database.
- */
- int transaction_exists;
-
- /**
* #GNUNET_NO if the @e connection was not suspended,
* #GNUNET_YES if the @e connection was suspended,
* #GNUNET_SYSERR if @e connection was resumed to as
@@ -409,16 +454,18 @@ sign_success_response (struct PayContext *pc)
json_t *refunds;
enum TALER_ErrorCode ec;
const char *errmsg;
-
- refunds = TM_get_refund_json (pc->mi, &pc->h_contract_terms, &ec, &errmsg);
-
- if (NULL == refunds) {
- return TMH_RESPONSE_make_internal_error (ec, errmsg);
- }
-
struct GNUNET_CRYPTO_EddsaSignature sig;
struct PaymentResponsePS mr;
+ refunds = TM_get_refund_json (pc->mi,
+ &pc->h_contract_terms,
+ &ec,
+ &errmsg);
+
+ if (NULL == refunds)
+ return TMH_RESPONSE_make_error (ec,
+ errmsg);
+
mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
mr.purpose.size = htonl (sizeof (mr));
mr.h_contract_terms = pc->h_contract_terms;
@@ -441,140 +488,23 @@ sign_success_response (struct PayContext *pc)
/**
- * Callback to handle a deposit permission's response.
+ * Resume payment processing with an error.
*
- * @param cls a `struct DepositConfirmation` (i.e. a pointer
- * into the global array of confirmations and an index for this call
- * in that array). That way, the last executed callback can detect
- * that no other confirmations are on the way, and can pack a response
- * for the wallet
- * @param http_status HTTP response code, #MHD_HTTP_OK
- * (200) for successful deposit; 0 if the exchange's reply is bogus (fails
- * to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param sign_key which key did the exchange use to sign the @a proof
- * @param proof the received JSON reply,
- * should be kept as proof (and, in case of errors, be forwarded to
- * the customer)
- */
+ * @param pc operation to resume
+ * @param http_status http status code to return
+ * @param ec taler error code to return
+ * @param msg human readable error message
+ */
static void
-deposit_cb (void *cls,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const json_t *proof)
+resume_pay_with_error (struct PayContext *pc,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *msg)
{
- struct DepositConfirmation *dc = cls;
- struct PayContext *pc = dc->pc;
- enum GNUNET_DB_QueryStatus qs;
-
- dc->dh = NULL;
- GNUNET_assert (GNUNET_YES == pc->suspended);
- pc->pending--;
- if (MHD_HTTP_OK != http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Deposit operation failed with HTTP code %u\n",
- http_status);
- /* Transaction failed; stop all other ongoing deposits */
- abort_deposit (pc);
- db->rollback (db->cls);
-
- if (NULL == proof)
- {
- /* We can't do anything meaningful here, the exchange did something wrong */
- resume_pay_with_response (pc,
- MHD_HTTP_SERVICE_UNAVAILABLE,
- TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:I, s:s}",
- "error", "exchange failed",
- "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
- "exchange-code", (json_int_t) ec,
- "exchange-http-status", (json_int_t) http_status,
- "hint", "The exchange provided an unexpected response"));
- }
- else
- {
- /* Forward error, adding the "coin_pub" for which the
- error was being generated */
- json_t *eproof;
-
- eproof = json_copy ((json_t *) proof);
- json_object_set_new (eproof,
- "coin_pub",
- GNUNET_JSON_from_data_auto (&dc->coin_pub));
- resume_pay_with_response (pc,
- http_status,
- TMH_RESPONSE_make_json (eproof));
- json_decref (eproof);
- }
- return;
- }
- /* store result to DB */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Storing successful payment for h_contract_terms `%s' and merchant `%s'\n",
- GNUNET_h2s (&pc->h_contract_terms),
- TALER_B2S (&pc->mi->pubkey));
- for (unsigned int i=0;i<MAX_RETRIES;i++)
- {
- qs = db->store_deposit (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey,
- &dc->coin_pub,
- pc->chosen_exchange,
- &dc->amount_with_fee,
- &dc->deposit_fee,
- &dc->refund_fee,
- sign_key,
- proof);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
- }
- if (0 > qs)
- {
- /* Special report if retries insufficient */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- /* internal error */
- abort_deposit (pc);
- db->rollback (db->cls);
- /* Forward error including 'proof' for the body */
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_DB_STORE_PAY_ERROR,
- "Merchant database error"));
- return;
- }
-
- if (0 != pc->pending)
- return; /* still more to do */
-
- qs = db->mark_proposal_paid (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey);
- if (0 > qs)
- {
- abort_deposit (pc);
- db->rollback (db->cls);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
- "Merchant database error: could not mark proposal as 'paid'"));
- return;
- }
- qs = db->commit (db->cls);
- if (0 > qs)
- {
- abort_deposit (pc);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
- "Merchant database error: could not commit"));
- return;
- }
resume_pay_with_response (pc,
- MHD_HTTP_OK,
- sign_success_response (pc));
+ http_status,
+ TMH_RESPONSE_make_error (ec,
+ msg));
}
@@ -613,6 +543,7 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
GNUNET_CRYPTO_rsa_signature_free (dc->ub_sig.rsa_signature);
dc->ub_sig.rsa_signature = NULL;
}
+ GNUNET_free_non_null (dc->exchange_url);
}
GNUNET_free_non_null (pc->dc);
if (NULL != pc->fo)
@@ -625,11 +556,6 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
MHD_destroy_response (pc->response);
pc->response = NULL;
}
- if (NULL != pc->chosen_exchange)
- {
- GNUNET_free (pc->chosen_exchange);
- pc->chosen_exchange = NULL;
- }
if (NULL != pc->contract_terms)
{
json_decref (pc->contract_terms);
@@ -643,99 +569,36 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
/**
- * Function called with the result of our exchange lookup.
+ * Check whether the amount paid is sufficient to cover
+ * the contract.
*
- * @param cls the `struct PayContext`
- * @param mh NULL if exchange was not found to be acceptable
- * @param wire_fee current applicable fee for dealing with @a mh, NULL if not available
- * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ * @param pc payment context to check
+ * @return taler error code, #TALER_EC_NONE if amount is sufficient
*/
-static void
-process_pay_with_exchange (void *cls,
- struct TALER_EXCHANGE_Handle *mh,
- const struct TALER_Amount *wire_fee,
- int exchange_trusted)
+static enum TALER_ErrorCode
+check_payment_sufficient (struct PayContext *pc)
{
- struct PayContext *pc = cls;
struct TALER_Amount acc_fee;
struct TALER_Amount acc_amount;
struct TALER_Amount wire_fee_delta;
struct TALER_Amount wire_fee_customer_contribution;
- const struct TALER_EXCHANGE_Keys *keys;
- enum GNUNET_DB_QueryStatus qs;
- pc->fo = NULL;
- GNUNET_assert (GNUNET_YES == pc->suspended);
- if (NULL == mh)
- {
- /* The exchange on offer is not in the set of our (trusted)
- exchanges. Reject the payment. */
- GNUNET_break_op (0);
- resume_pay_with_response (pc,
- MHD_HTTP_PRECONDITION_FAILED,
- TMH_RESPONSE_make_external_error (TALER_EC_PAY_EXCHANGE_REJECTED,
- "exchange not supported"));
- return;
- }
- pc->mh = mh;
- keys = TALER_EXCHANGE_get_keys (mh);
- if (NULL == keys)
- {
- GNUNET_break (0);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
- "no keys"));
- return;
- }
-
- /* Total up the fees and the value of the deposited coins! */
GNUNET_assert (0 != pc->coins_cnt);
for (unsigned int i=0;i<pc->coins_cnt;i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
- const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
- denom_details = TALER_EXCHANGE_get_denomination_key (keys,
- &dc->denom);
- if (NULL == denom_details)
- {
- GNUNET_break_op (0);
- resume_pay_with_response (pc,
- MHD_HTTP_BAD_REQUEST,
- TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, s:o}",
- "error", "denomination not found",
- "code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
- "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key),
- "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh)));
- return;
- }
- if (GNUNET_OK !=
- TMH_AUDITORS_check_dk (mh,
- denom_details,
- exchange_trusted))
- {
- GNUNET_break_op (0);
- resume_pay_with_response (pc,
- MHD_HTTP_BAD_REQUEST,
- TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o}",
- "error", "invalid denomination",
- "code", (json_int_t) TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE,
- "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key)));
- return;
- }
- dc->deposit_fee = denom_details->fee_deposit;
- dc->refund_fee = denom_details->fee_refund;
+ GNUNET_assert (GNUNET_YES == dc->found_in_db);
if (0 == i)
{
- acc_fee = denom_details->fee_deposit;
+ acc_fee = dc->deposit_fee;
acc_amount = dc->amount_with_fee;
}
else
{
if ( (GNUNET_OK !=
TALER_amount_add (&acc_fee,
- &denom_details->fee_deposit,
+ &dc->deposit_fee,
&acc_fee)) ||
(GNUNET_OK !=
TALER_amount_add (&acc_amount,
@@ -744,11 +607,7 @@ process_pay_with_exchange (void *cls,
{
GNUNET_break_op (0);
/* Overflow in these amounts? Very strange. */
- resume_pay_with_response (pc,
- MHD_HTTP_BAD_REQUEST,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts"));
- return;
+ return TALER_EC_PAY_AMOUNT_OVERFLOW;
}
}
if (1 ==
@@ -757,33 +616,22 @@ process_pay_with_exchange (void *cls,
{
GNUNET_break_op (0);
/* fee higher than residual coin value, makes no sense. */
- resume_pay_with_response (pc,
- MHD_HTTP_BAD_REQUEST,
- TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, s:o}",
- "hint", "fee higher than coin value",
- "code", (json_int_t) TALER_EC_PAY_FEES_EXCEED_PAYMENT,
- "f" /* FIXME */, TALER_JSON_from_amount (&dc->amount_with_fee),
- "fee_deposit", TALER_JSON_from_amount (&denom_details->fee_deposit)));
- return;
+ return TALER_EC_PAY_FEES_EXCEED_PAYMENT;
}
}
/* Now compare exchange wire fee compared to what we are willing to pay */
if (GNUNET_YES !=
- TALER_amount_cmp_currency (wire_fee,
+ TALER_amount_cmp_currency (&pc->total_wire_fee,
&pc->max_wire_fee))
{
GNUNET_break (0);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH,
- "wire_fee"));
- return;
+ return TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH;
}
if (GNUNET_OK ==
TALER_amount_subtract (&wire_fee_delta,
- wire_fee,
+ &pc->total_wire_fee,
&pc->max_wire_fee))
{
/* Actual wire fee is indeed higher than our maximum, compute
@@ -795,7 +643,7 @@ process_pay_with_exchange (void *cls,
else
{
GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (wire_fee->currency,
+ TALER_amount_get_zero (pc->total_wire_fee.currency,
&wire_fee_customer_contribution));
}
@@ -820,11 +668,7 @@ process_pay_with_exchange (void *cls,
&pc->amount))
{
GNUNET_break (0);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_AMOUNT_OVERFLOW,
- "overflow"));
- return;
+ return TALER_EC_PAY_AMOUNT_OVERFLOW;
}
/* add wire fee contribution to the total */
if (GNUNET_OK ==
@@ -837,11 +681,7 @@ process_pay_with_exchange (void *cls,
&total_needed))
{
GNUNET_break_op (0);
- resume_pay_with_response (pc,
- MHD_HTTP_METHOD_NOT_ACCEPTABLE,
- TMH_RESPONSE_make_external_error (TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
- "insufficient funds (including excessive exchange fees to be covered by customer)"));
- return;
+ return TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES;
}
}
else
@@ -871,11 +711,7 @@ process_pay_with_exchange (void *cls,
&wire_fee_customer_contribution))
{
GNUNET_break_op (0);
- resume_pay_with_response (pc,
- MHD_HTTP_METHOD_NOT_ACCEPTABLE,
- TMH_RESPONSE_make_external_error (TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
- "insufficient funds (including excessive exchange fees to be covered by customer)"));
- return;
+ return TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES;
}
}
@@ -884,173 +720,310 @@ process_pay_with_exchange (void *cls,
&pc->amount))
{
GNUNET_break_op (0);
- resume_pay_with_response (pc,
- MHD_HTTP_METHOD_NOT_ACCEPTABLE,
- TMH_RESPONSE_make_external_error (TALER_EC_PAY_PAYMENT_INSUFFICIENT,
- "insufficient funds"));
- return;
+ return TALER_EC_PAY_PAYMENT_INSUFFICIENT;
}
}
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Exchange and fee structure OK. Initiating deposit operation for coins\n");
+ return TALER_EC_NONE;
+}
- if (GNUNET_OK != db->start (db->cls))
+/**
+ * Generate full error response based on the @a ec
+ *
+ * @param pc context for which to generate the error
+ * @param ec error code identifying the issue
+ */
+static void
+generate_error_response (struct PayContext *pc,
+ enum TALER_ErrorCode ec)
+{
+ switch (ec)
{
+ case TALER_EC_PAY_AMOUNT_OVERFLOW:
+ resume_pay_with_error (pc,
+ MHD_HTTP_BAD_REQUEST,
+ ec,
+ "Overflow adding up amounts");
+ break;
+ case TALER_EC_PAY_FEES_EXCEED_PAYMENT:
+ resume_pay_with_error (pc,
+ MHD_HTTP_BAD_REQUEST,
+ ec,
+ "Deposit fees exceed coin's contribution");
+ break;
+ case TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES:
+ resume_pay_with_error (pc,
+ MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+ ec,
+ "insufficient funds (including excessive exchange fees to be covered by customer)");
+ break;
+ case TALER_EC_PAY_PAYMENT_INSUFFICIENT:
+ resume_pay_with_error (pc,
+ MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+ ec,
+ "insufficient funds");
+ break;
+ case TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH:
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ ec,
+ "wire_fee currency does not match");
+ break;
+ default:
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ ec,
+ "unexpected error code");
GNUNET_break (0);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_json_pack ("{s:s, s:I}",
- "hint", "Merchant database error: could not start transaction",
- "code", (json_int_t) TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR));
- return;
+ break;
}
+}
- /* Check if transaction is already known, if not store it. */
+
+/**
+ * Find the exchange we need to talk to for the next
+ * pending deposit permission.
+ *
+ * @param pc payment context we are processing
+ */
+static void
+find_next_exchange (struct PayContext *pc);
+
+
+/**
+ * Begin of the DB transaction. If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param pc payment context to transact
+ */
+static void
+begin_transaction (struct PayContext *pc);
+
+
+/**
+ * Callback to handle a deposit permission's response.
+ *
+ * @param cls a `struct DepositConfirmation` (i.e. a pointer
+ * into the global array of confirmations and an index for this call
+ * in that array). That way, the last executed callback can detect
+ * that no other confirmations are on the way, and can pack a response
+ * for the wallet
+ * @param http_status HTTP response code, #MHD_HTTP_OK
+ * (200) for successful deposit; 0 if the exchange's reply is bogus (fails
+ * to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param sign_key which key did the exchange use to sign the @a proof
+ * @param proof the received JSON reply,
+ * should be kept as proof (and, in case of errors, be forwarded to
+ * the customer)
+ */
+static void
+deposit_cb (void *cls,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const struct TALER_ExchangePublicKeyP *sign_key,
+ const json_t *proof)
+{
+ struct DepositConfirmation *dc = cls;
+ struct PayContext *pc = dc->pc;
+ enum GNUNET_DB_QueryStatus qs;
+
+ dc->dh = NULL;
+ GNUNET_assert (GNUNET_YES == pc->suspended);
+ pc->pending_at_ce--;
+ if (MHD_HTTP_OK != http_status)
{
- struct GNUNET_HashCode h_xwire;
- struct GNUNET_TIME_Absolute xtimestamp;
- struct GNUNET_TIME_Absolute xrefund;
- struct TALER_Amount xtotal_amount;
-
- qs = db->find_transaction (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey,
- &h_xwire,
- &xtimestamp,
- &xrefund,
- &xtotal_amount);
- if (0 > qs)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Deposit operation failed with HTTP code %u\n",
+ http_status);
+ /* Transaction failed; stop all other ongoing deposits */
+ abort_deposit (pc);
+ db->rollback (db->cls);
+
+ if (NULL == proof)
{
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- /* FIXME: factor common logic of these calls into a function! */
+ /* We can't do anything meaningful here, the exchange did something wrong */
resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
- "code", (json_int_t) TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
- "hint", "Merchant database error"));
- return;
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:I, s:s}",
+ "error", "exchange failed",
+ "code", (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+ "exchange-code", (json_int_t) ec,
+ "exchange-http-status", (json_int_t) http_status,
+ "hint", "The exchange provided an unexpected response"));
}
- if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == pc->transaction_exists) &&
- ( (0 != memcmp (&h_xwire,
- &pc->mi->h_wire,
- sizeof (struct GNUNET_HashCode))) ||
- (xtimestamp.abs_value_us != pc->timestamp.abs_value_us) ||
- (xrefund.abs_value_us != pc->refund_deadline.abs_value_us) ||
- (0 != TALER_amount_cmp (&xtotal_amount,
- &pc->amount) ) ) )
+ else
{
- GNUNET_break (0);
- /* FIXME: factor common logic of these calls into a function! */
+ /* Forward error, adding the "coin_pub" for which the
+ error was being generated */
+ json_t *eproof;
+
+ eproof = json_copy ((json_t *) proof);
+ json_object_set_new (eproof,
+ "coin_pub",
+ GNUNET_JSON_from_data_auto (&dc->coin_pub));
resume_pay_with_response (pc,
- MHD_HTTP_BAD_REQUEST,
- TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
- "code", (json_int_t) TALER_EC_PAY_DB_TRANSACTION_ID_CONFLICT,
- "hint", "Transaction ID reused with different transaction details"));
- return;
+ http_status,
+ TMH_RESPONSE_make_json (eproof));
+ json_decref (eproof);
}
+ return;
}
-
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->transaction_exists)
+ /* store result to DB */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Storing successful payment for h_contract_terms `%s' and merchant `%s'\n",
+ GNUNET_h2s (&pc->h_contract_terms),
+ TALER_B2S (&pc->mi->pubkey));
+ /* NOTE: not run in any transaction block, simply as a
+ transaction by itself! */
+ qs = db->store_deposit (db->cls,
+ &pc->h_contract_terms,
+ &pc->mi->pubkey,
+ &dc->coin_pub,
+ dc->exchange_url,
+ &dc->amount_with_fee,
+ &dc->deposit_fee,
+ &dc->refund_fee,
+ sign_key,
+ proof);
+ if (0 > qs)
{
- struct GNUNET_TIME_Absolute now;
- enum GNUNET_DB_QueryStatus qs_st;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Dealing with new transaction `%s'\n",
- GNUNET_h2s (&pc->h_contract_terms));
-
- now = GNUNET_TIME_absolute_get ();
- if (now.abs_value_us > pc->pay_deadline.abs_value_us)
+ /* Special report if retries insufficient */
+ abort_deposit (pc);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- /* Time expired, we don't accept this payment now! */
- const char *pd_str;
-
- pd_str = GNUNET_STRINGS_absolute_time_to_string (pc->pay_deadline);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Attempt to pay coins for expired contract. Deadline: `%s'\n",
- pd_str);
- resume_pay_with_response (pc,
- MHD_HTTP_BAD_REQUEST,
- TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
- "code", (json_int_t) TALER_EC_PAY_OFFER_EXPIRED,
- "hint", "The time to pay for this contract has expired."));
+ begin_transaction (pc);
return;
}
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ /* Forward error including 'proof' for the body */
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_STORE_PAY_ERROR,
+ "Merchant database error");
+ return;
+ }
+ dc->found_in_db = GNUNET_YES;
+ pc->pending--;
- qs_st = db->store_transaction (db->cls,
- &pc->h_contract_terms,
- &pc->mi->pubkey,
- &pc->mi->h_wire,
- pc->timestamp,
- pc->refund_deadline,
- &pc->amount);
- /* Only retry if SOFT error occurred. Exit in case of OK or HARD failure */
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs_st)
- {
- db->rollback (db->cls);
- GNUNET_break (0); // FIXME: implement proper retries!
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
- "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
- "hint", "Soft merchant database error: retries not implemented"));
- return;
- }
- /* Exit in case of HARD failure */
- if (GNUNET_DB_STATUS_HARD_ERROR == qs_st)
- {
- GNUNET_break (0);
- db->rollback (db->cls);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
- "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
- "hint", "Merchant database error: hard error while storing transaction"));
- }
+ if (0 != pc->pending_at_ce)
+ return; /* still more to do with current exchange */
+ find_next_exchange (pc);
+}
+
+
+/**
+ * Function called with the result of our exchange lookup.
+ *
+ * @param cls the `struct PayContext`
+ * @param mh NULL if exchange was not found to be acceptable
+ * @param wire_fee current applicable fee for dealing with @a mh, NULL if not available
+ * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ */
+static void
+process_pay_with_exchange (void *cls,
+ struct TALER_EXCHANGE_Handle *mh,
+ const struct TALER_Amount *wire_fee,
+ int exchange_trusted)
+{
+ struct PayContext *pc = cls;
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ pc->fo = NULL;
+ GNUNET_assert (GNUNET_YES == pc->suspended);
+ if (NULL == mh)
+ {
+ /* The exchange on offer is not in the set of our (trusted)
+ exchanges. Reject the payment. */
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_PAY_EXCHANGE_REJECTED,
+ "exchange not supported");
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&pc->total_wire_fee,
+ &pc->total_wire_fee,
+ wire_fee))
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_PAY_EXCHANGE_REJECTED,
+ "exchange charges incompatible wire fee");
+ return;
+ }
+ pc->mh = mh;
+ keys = TALER_EXCHANGE_get_keys (mh);
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
+ "no keys");
+ return;
+ }
- /**
- * Break if we couldn't modify one, and only one line; this
- * includes hard errors.
- */
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs_st)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected query status %d while storing /pay transaction!\n",
- (int) qs_st);
- resume_pay_with_response (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
- "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
- "hint", "Merchant database error: failed to store transaction"));
- return;
- }
- } /* end of if (GNUNET_NO == pc->transaction_exists) */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found transaction data for proposal `%s' of merchant `%s', initiating deposits\n",
GNUNET_h2s (&pc->h_contract_terms),
TALER_B2S (&pc->mi->pubkey));
-
- /* Initiate /deposit operation for all coins */
+ /* Initiate /deposit operation for all coins of
+ the current exchange (!) */
+ GNUNET_assert (0 == pc->pending_at_ce);
for (unsigned int i=0;i<pc->coins_cnt;i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
if (GNUNET_YES == dc->found_in_db)
continue;
+ if (0 != strcmp (dc->exchange_url,
+ pc->current_exchange))
+ continue;
+ denom_details = TALER_EXCHANGE_get_denomination_key (keys,
+ &dc->denom);
+ if (NULL == denom_details)
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_response (pc,
+ MHD_HTTP_BAD_REQUEST,
+ TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, s:o}",
+ "error", "denomination not found",
+ "code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
+ "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key),
+ "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh)));
+ return;
+ }
+ if (GNUNET_OK !=
+ TMH_AUDITORS_check_dk (mh,
+ denom_details,
+ exchange_trusted))
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_response (pc,
+ MHD_HTTP_BAD_REQUEST,
+ TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o}",
+ "error", "invalid denomination",
+ "code", (json_int_t) TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE,
+ "denom_pub", GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key)));
+ return;
+ }
+ dc->deposit_fee = denom_details->fee_deposit;
+ dc->refund_fee = denom_details->fee_refund;
+ dc->wire_fee = *wire_fee;
+
GNUNET_assert (NULL != pc->mi->j_wire);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Timing for this payment, wire_deadline: %llu, refund_deadline: %llu\n",
(unsigned long long) pc->wire_transfer_deadline.abs_value_us,
(unsigned long long) pc->refund_deadline.abs_value_us);
-
dc->dh = TALER_EXCHANGE_deposit (mh,
&dc->amount_with_fee,
pc->wire_transfer_deadline,
@@ -1070,7 +1043,6 @@ process_pay_with_exchange (void *cls,
/* Signature was invalid. If the exchange was unavailable,
* we'd get that information in the callback. */
GNUNET_break_op (0);
- db->rollback (db->cls);
resume_pay_with_response (pc,
MHD_HTTP_UNAUTHORIZED,
TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:i}",
@@ -1080,7 +1052,47 @@ process_pay_with_exchange (void *cls,
"coin_idx", i));
return;
}
+ pc->pending_at_ce++;
+ }
+}
+
+
+/**
+ * Find the exchange we need to talk to for the next
+ * pending deposit permission.
+ *
+ * @param pc payment context we are processing
+ */
+static void
+find_next_exchange (struct PayContext *pc)
+{
+ for (unsigned int i=0;i<pc->coins_cnt;i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ if (GNUNET_YES != dc->found_in_db)
+ {
+ pc->current_exchange = dc->exchange_url;
+ pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
+ pc->mi->wire_method,
+ &process_pay_with_exchange,
+ pc);
+ if (NULL == pc->fo)
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_EXCHANGE_FAILED,
+ "Failed to lookup exchange by URL");
+ return;
+ }
+ return;
+ }
}
+ pc->current_exchange = NULL;
+ /* We are done with all the HTTP requests, go back and try
+ the 'big' database transaction! (It should work now!) */
+ begin_transaction (pc);
}
@@ -1103,10 +1115,10 @@ handle_pay_timeout (void *cls)
TMH_EXCHANGES_find_exchange_cancel (pc->fo);
pc->fo = NULL;
}
- resume_pay_with_response (pc,
- MHD_HTTP_SERVICE_UNAVAILABLE,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_EXCHANGE_TIMEOUT,
- "exchange not reachable"));
+ resume_pay_with_error (pc,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_PAY_EXCHANGE_TIMEOUT,
+ "exchange not reachable");
}
@@ -1130,6 +1142,7 @@ check_coin_paid (void *cls,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_Amount *refund_fee,
+ // FIXME: also store AND fetch wire fee!
const json_t *exchange_proof)
{
struct PayContext *pc = cls;
@@ -1144,6 +1157,9 @@ check_coin_paid (void *cls,
for (unsigned int i=0;i<pc->coins_cnt;i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
+
+ if (GNUNET_YES == dc->found_in_db)
+ continue; /* processed earlier */
/* Get matching coin from results*/
if ( (0 != memcmp (coin_pub,
&dc->coin_pub,
@@ -1151,16 +1167,35 @@ check_coin_paid (void *cls,
(0 != TALER_amount_cmp (amount_with_fee,
&dc->amount_with_fee)) )
continue;
-
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Coin (%s) already found in our DB.\n",
- TALER_b2s (coin_pub, sizeof (*coin_pub)));
-
+ TALER_b2s (coin_pub,
+ sizeof (*coin_pub)));
+ if (GNUNET_OK !=
+ TALER_amount_add (&pc->total_paid,
+ &pc->total_paid,
+ amount_with_fee))
+ {
+ /* We accepted this coin for payment on this contract before,
+ and now we can't even add the amount!? */
+ GNUNET_break (0);
+ continue;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&pc->total_fees_paid,
+ &pc->total_fees_paid,
+ deposit_fee))
+ {
+ /* We accepted this coin for payment on this contract before,
+ and now we can't even add the amount!? */
+ GNUNET_break (0);
+ continue;
+ }
+ dc->deposit_fee = *deposit_fee;
+ dc->refund_fee = *refund_fee;
+ // dc->wire_fee = *wire_fee; // TBD...
+ dc->amount_with_fee = *amount_with_fee;
dc->found_in_db = GNUNET_YES;
- /**
- * What happens if a (mad) wallet sends new coins on a
- * contract that it already paid for?
- */
pc->pending--;
}
}
@@ -1187,15 +1222,16 @@ parse_pay (struct MHD_Connection *connection,
json_t *coin;
json_t *merchant;
unsigned int coins_index;
- const char *chosen_exchange;
const char *order_id;
struct TALER_MerchantPublicKeyP merchant_pub;
int res;
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_json ("coins",
+ &coins),
+ GNUNET_JSON_spec_string ("order_id",
+ &order_id),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
GNUNET_JSON_spec_end()
};
enum GNUNET_DB_QueryStatus qs;
@@ -1295,7 +1331,6 @@ parse_pay (struct MHD_Connection *connection,
GNUNET_STRINGS_data_to_string_alloc (&pc->mi->pubkey,
sizeof (pc->mi->pubkey)));
- pc->chosen_exchange = GNUNET_strdup (chosen_exchange);
{
struct GNUNET_JSON_Specification espec[] = {
GNUNET_JSON_spec_absolute_time ("refund_deadline",
@@ -1379,6 +1414,10 @@ parse_pay (struct MHD_Connection *connection,
TALER_amount_get_zero (pc->max_fee.currency,
&pc->max_wire_fee));
}
+ /* Initialize wire fee total */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (pc->max_fee.currency,
+ &pc->total_wire_fee));
if (NULL != json_object_get (pc->contract_terms,
"wire_fee_amortization"))
{
@@ -1420,12 +1459,20 @@ parse_pay (struct MHD_Connection *connection,
json_array_foreach (coins, coins_index, coin)
{
struct DepositConfirmation *dc = &pc->dc[coins_index];
+ const char *exchange_url;
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),
+ TALER_JSON_spec_denomination_public_key ("denom_pub",
+ &dc->denom),
+ TALER_JSON_spec_amount ("contribution",
+ &dc->amount_with_fee),
+ GNUNET_JSON_spec_string ("exchange_url",
+ &exchange_url),
+ 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()
};
@@ -1436,9 +1483,9 @@ parse_pay (struct MHD_Connection *connection,
{
GNUNET_JSON_parse_free (spec);
GNUNET_break_op (0);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ return res;
}
-
+ dc->exchange_url = GNUNET_strdup (exchange_url);
dc->index = coins_index;
dc->pc = pc;
}
@@ -1449,29 +1496,80 @@ parse_pay (struct MHD_Connection *connection,
/**
- * Process a payment for a proposal.
+ * Function called with information about a refund.
+ * Check if this coin was claimed by the wallet for the
+ * transaction, and if so add the refunded amount to the
+ * pc's "total_refunded" amount.
*
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return value to return to MHD (#MHD_NO to drop connection,
- * #MHD_YES to keep handling it)
+ * @param cls closure with a `struct PayContext`
+ * @param coin_pub public coin from which the refund comes from
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explaination of the refund
+ * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_fee cost of this refund operation
*/
-static int
-handler_pay_json (struct MHD_Connection *connection,
- const json_t *root,
- struct PayContext *pc)
+static void
+check_coin_refunded (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const char *reason,
+ const struct TALER_Amount *refund_amount,
+ const struct TALER_Amount *refund_fee)
{
- int ret;
+ struct PayContext *pc = cls;
+
+ /* FIXME: to be implemented (#5158) */
+ (void) pc;
+}
+
+
+/**
+ * Begin of the DB transaction. If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param pc payment context to transact
+ */
+static void
+begin_transaction (struct PayContext *pc)
+{
enum GNUNET_DB_QueryStatus qs;
- ret = parse_pay (connection,
- root,
- pc);
- if (GNUNET_OK != ret)
- return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ /* Avoid re-trying transactions on soft errors forever! */
+ if (pc->retry_counter++ > MAX_RETRIES)
+ {
+ GNUNET_break (0);
+ resume_pay_with_response (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+ "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
+ "hint", "Soft merchant database error: retry counter exceeded"));
+ return;
+ }
+
+ GNUNET_assert (GNUNET_YES == pc->suspended);
- /* Check if this payment attempt has already succeeded */
+ /* First, try to see if we have all we need already done */
+ if (GNUNET_OK != db->start (db->cls))
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error (could not start transaction)");
+ return;
+ }
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_get_zero (pc->amount.currency,
+ &pc->total_paid));
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_get_zero (pc->amount.currency,
+ &pc->total_fees_paid));
+ GNUNET_break (GNUNET_OK ==
+ TALER_amount_get_zero (pc->amount.currency,
+ &pc->total_refunded));
+
+ /* Check if some of these coins already succeeded */
qs = db->find_payments (db->cls,
&pc->h_contract_terms,
&pc->mi->pubkey,
@@ -1479,50 +1577,269 @@ handler_pay_json (struct MHD_Connection *connection,
pc);
if (0 > qs)
{
- /* single, read-only SQL statements should never cause
- serialization problems */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ db->rollback (db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ begin_transaction (pc);
+ return;
+ }
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return TMH_RESPONSE_reply_internal_error (connection,
- TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
- "Merchant database error");
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error");
+ return;
+ }
+
+ /* Check if we refunded some of the coins */
+ qs = db->get_refunds_from_contract_terms_hash (db->cls,
+ &pc->mi->pubkey,
+ &pc->h_contract_terms,
+ &check_coin_refunded,
+ pc);
+ if (0 > qs)
+ {
+ db->rollback (db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ begin_transaction (pc);
+ return;
+ }
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ "Merchant database error");
+ return;
}
+
+ /* FIXME: check if wallet is going for a refund,
+ (on aborted operation), or for a payment! #5158 */
+
+
+ /* Final termination case: all coins already known, just
+ generate ultimate outcome. */
if (0 == pc->pending)
{
- struct MHD_Response *resp;
- int ret;
+ enum TALER_ErrorCode ec;
+
+ ec = check_payment_sufficient (pc);
+ if (TALER_EC_NONE == ec)
+ {
+ /* Payment succeeded, commit! */
+ qs = db->mark_proposal_paid (db->cls,
+ &pc->h_contract_terms,
+ &pc->mi->pubkey);
+ if (0 <= qs)
+ qs = db->commit (db->cls);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ begin_transaction (pc);
+ return;
+ }
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+ "Merchant database error: could not mark proposal as 'paid'");
+ return;
+ }
+ resume_pay_with_response (pc,
+ MHD_HTTP_OK,
+ sign_success_response (pc));
+ return;
+ }
+ generate_error_response (pc,
+ ec);
+ return;
+ }
+
+
+ /* Check if transaction is already known, if not store it. */
+ {
+ struct GNUNET_HashCode h_xwire;
+ struct GNUNET_TIME_Absolute xtimestamp;
+ struct GNUNET_TIME_Absolute xrefund;
+ struct TALER_Amount xtotal_amount;
+
+ qs = db->find_transaction (db->cls,
+ &pc->h_contract_terms,
+ &pc->mi->pubkey,
+ &h_xwire,
+ &xtimestamp,
+ &xrefund,
+ &xtotal_amount);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ db->rollback (db->cls);
+ begin_transaction (pc);
+ return;
+ }
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ db->rollback (db->cls);
+ resume_pay_with_response (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+ "code", (json_int_t) TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+ "hint", "Merchant database error"));
+ return;
+ }
+ if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
+ ( (0 != memcmp (&h_xwire,
+ &pc->mi->h_wire,
+ sizeof (struct GNUNET_HashCode))) ||
+ (xtimestamp.abs_value_us != pc->timestamp.abs_value_us) ||
+ (xrefund.abs_value_us != pc->refund_deadline.abs_value_us) ||
+ (0 != TALER_amount_cmp (&xtotal_amount,
+ &pc->amount) ) ) )
+ {
+ GNUNET_break (0);
+ db->rollback (db->cls);
+ resume_pay_with_response (pc,
+ MHD_HTTP_BAD_REQUEST,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+ "code", (json_int_t) TALER_EC_PAY_DB_TRANSACTION_ID_CONFLICT,
+ "hint", "Transaction ID reused with different transaction details"));
+ return;
+ }
+ }
+
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ struct GNUNET_TIME_Absolute now;
+ enum GNUNET_DB_QueryStatus qs_st;
- /* Payment succeeded in the past; take short cut
- and accept immediately */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Payment succeeded in the past; taking short cut");
- resp = sign_success_response (pc);
- ret = MHD_queue_response (connection,
- MHD_HTTP_OK,
- resp);
- MHD_destroy_response (resp);
- return ret;
+ "Dealing with new transaction `%s'\n",
+ GNUNET_h2s (&pc->h_contract_terms));
+
+ now = GNUNET_TIME_absolute_get ();
+ if (now.abs_value_us > pc->pay_deadline.abs_value_us)
+ {
+ /* Time expired, we don't accept this payment now! */
+ const char *pd_str;
+
+ pd_str = GNUNET_STRINGS_absolute_time_to_string (pc->pay_deadline);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Attempt to pay coins for expired contract. Deadline: `%s'\n",
+ pd_str);
+ db->rollback (db->cls);
+ resume_pay_with_response (pc,
+ MHD_HTTP_BAD_REQUEST,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+ "code", (json_int_t) TALER_EC_PAY_OFFER_EXPIRED,
+ "hint", "The time to pay for this contract has expired."));
+ return;
+ }
+
+ qs_st = db->store_transaction (db->cls,
+ &pc->h_contract_terms,
+ &pc->mi->pubkey,
+ &pc->mi->h_wire,
+ pc->timestamp,
+ pc->refund_deadline,
+ &pc->amount);
+ /* Only retry if SOFT error occurred. Exit in case of OK or HARD failure */
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs_st)
+ {
+ db->rollback (db->cls);
+ begin_transaction (pc);
+ return;
+ }
+ /* Exit in case of HARD failure */
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs_st)
+ {
+ GNUNET_break (0);
+ db->rollback (db->cls);
+ resume_pay_with_response (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+ "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
+ "hint", "Merchant database error: hard error while storing transaction"));
+ }
+
+ /**
+ * Break if we couldn't modify one, and only one line; this
+ * includes hard errors.
+ */
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs_st)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected query status %d while storing /pay transaction!\n",
+ (int) qs_st);
+ db->rollback (db->cls);
+ resume_pay_with_response (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+ "code", (json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
+ "hint", "Merchant database error: failed to store transaction"));
+ return;
+ }
+
+ qs = db->commit (db->cls);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ db->rollback (db->cls);
+ begin_transaction (pc);
+ return;
+ }
+ resume_pay_with_error (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+ "Merchant database error: could not commit");
+ return;
+ }
+ } /* end of if (GNUNET_NO == qs) */
+ else
+ {
+ /* transaction record already existed, we made no DB changes,
+ so we can just rollback */
+ db->rollback (db->cls);
}
+
+ /* Ok, we need to first go to the network.
+ Do that interaction in *tiny* transactions. */
+ find_next_exchange (pc);
+}
- MHD_suspend_connection (connection);
- pc->suspended = GNUNET_YES;
- /* Find the responsible exchange, this may take a while... */
- pc->fo = TMH_EXCHANGES_find_exchange (pc->chosen_exchange,
- pc->mi->wire_method,
- &process_pay_with_exchange,
- pc);
+/**
+ * Process a payment for a proposal.
+ *
+ * @param connection HTTP connection we are receiving payment on
+ * @param root JSON upload with payment data
+ * @param pc context we use to handle the payment
+ * @return value to return to MHD (#MHD_NO to drop connection,
+ * #MHD_YES to keep handling it)
+ */
+static int
+handler_pay_json (struct MHD_Connection *connection,
+ const json_t *root,
+ struct PayContext *pc)
+{
+ int ret;
- /* ... so we suspend connection until the last coin has been ack'd
- or until we have encountered a hard error. Eventually, we will
- resume the connection and send back a response using
- #resume_pay_with_response(). */
+ ret = parse_pay (connection,
+ root,
+ pc);
+ if (GNUNET_OK != ret)
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ MHD_suspend_connection (connection);
+ pc->suspended = GNUNET_YES;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Suspending /pay handling while working with the exchange\n");
pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
&handle_pay_timeout,
pc);
+ begin_transaction (pc);
return MHD_YES;
}
@@ -1587,13 +1904,7 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
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,
diff --git a/src/backend/taler-merchant-httpd_responses.c b/src/backend/taler-merchant-httpd_responses.c
index 1417fa24..71d04064 100644
--- a/src/backend/taler-merchant-httpd_responses.c
+++ b/src/backend/taler-merchant-httpd_responses.c
@@ -181,11 +181,10 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection,
* @return a MHD response object
*/
struct MHD_Response *
-TMH_RESPONSE_make_internal_error (enum TALER_ErrorCode ec,
- const char *hint)
+TMH_RESPONSE_make_error (enum TALER_ErrorCode ec,
+ const char *hint)
{
- return TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:s}",
- "error", "internal error",
+ return TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
"code", (json_int_t) ec,
"hint", hint);
}
@@ -206,8 +205,7 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection,
{
return TMH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- "{s:s, s:I, s:s}",
- "error", "internal error",
+ "{s:I, s:s}",
"code", (json_int_t) ec,
"hint", hint);
}
@@ -354,32 +352,13 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection,
{
return TMH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
- "{s:s, s:I, s:s}",
- "error", "client error",
+ "{s:I, s:s}",
"code", (json_int_t) ec,
"hint", hint);
}
/**
- * Create a response indicating an external error.
- *
- * @param ec error code to return
- * @param hint hint about the internal error's nature
- * @return a MHD response object
- */
-struct MHD_Response *
-TMH_RESPONSE_make_external_error (enum TALER_ErrorCode ec,
- const char *hint)
-{
- return TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:s}",
- "error", "client error",
- "code", (json_int_t) ec,
- "hint", hint);
-}
-
-
-/**
* Send a response indicating a missing argument.
*
* @param connection the MHD connection to use
@@ -394,7 +373,7 @@ TMH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection,
{
return TMH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_BAD_REQUEST,
- "{ s:s, s:I, s:s}",
+ "{s:s, s:I, s:s}",
"error", "missing parameter",
"code", (json_int_t) ec,
"parameter", param_name);
diff --git a/src/backend/taler-merchant-httpd_responses.h b/src/backend/taler-merchant-httpd_responses.h
index a96b696d..3dbd0049 100644
--- a/src/backend/taler-merchant-httpd_responses.h
+++ b/src/backend/taler-merchant-httpd_responses.h
@@ -143,20 +143,6 @@ TMH_RESPONSE_reply_bad_request (struct MHD_Connection *connection,
/**
- * Send a response indicating an internal error.
- *
- * @param connection the MHD connection to use
- * @param ec error code to return
- * @param hint hint about the internal error's nature
- * @return a MHD result code
- */
-int
-TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection,
- enum TALER_ErrorCode ec,
- const char *hint);
-
-
-/**
* Create a response indicating an internal error.
*
* @param ec error code to return
@@ -183,15 +169,29 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection,
/**
- * Create a response indicating an external error.
+ * Send a response indicating an internal error.
+ *
+ * @param connection the MHD connection to use
+ * @param ec error code to return
+ * @param hint hint about the internal error's nature
+ * @return a MHD result code
+ */
+int
+TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const char *hint);
+
+
+/**
+ * Create a response indicating an error.
*
* @param ec error code to return
* @param hint hint about the internal error's nature
* @return a MHD response object
*/
struct MHD_Response *
-TMH_RESPONSE_make_external_error (enum TALER_ErrorCode ec,
- const char *hint);
+TMH_RESPONSE_make_error (enum TALER_ErrorCode ec,
+ const char *hint);
/**
diff --git a/src/backend/taler-merchant-httpd_track-transaction.c b/src/backend/taler-merchant-httpd_track-transaction.c
index c60daf61..27d2d06e 100644
--- a/src/backend/taler-merchant-httpd_track-transaction.c
+++ b/src/backend/taler-merchant-httpd_track-transaction.c
@@ -640,8 +640,8 @@ wtid_cb (void *cls,
resume_track_transaction_with_response
(tcc->tctx,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSACTION_DB_FETCH_FAILED,
- "Fail to query database about proofs"));
+ TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSACTION_DB_FETCH_FAILED,
+ "Fail to query database about proofs"));
return;
}
/* WARNING: if two transactions got aggregated under the same
@@ -878,7 +878,7 @@ handle_track_transaction_timeout (void *cls)
}
resume_track_transaction_with_response (tctx,
MHD_HTTP_SERVICE_UNAVAILABLE,
- TMH_RESPONSE_make_internal_error (TALER_EC_PAY_EXCHANGE_TIMEOUT,
+ TMH_RESPONSE_make_error (TALER_EC_PAY_EXCHANGE_TIMEOUT,
"exchange not reachable"));
}
diff --git a/src/backend/taler-merchant-httpd_track-transfer.c b/src/backend/taler-merchant-httpd_track-transfer.c
index 0e84b1d7..3d4b4184 100644
--- a/src/backend/taler-merchant-httpd_track-transfer.c
+++ b/src/backend/taler-merchant-httpd_track-transfer.c
@@ -750,8 +750,8 @@ wire_transfer_cb (void *cls,
resume_track_transfer_with_response
(rctx,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
- "Fail to elaborate the response."));
+ TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
+ "Fail to elaborate the response."));
return;
}
@@ -818,7 +818,7 @@ handle_track_transfer_timeout (void *cls)
}
resume_track_transfer_with_response (rctx,
MHD_HTTP_SERVICE_UNAVAILABLE,
- TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT,
+ TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT,
"exchange not reachable"));
}
@@ -845,8 +845,8 @@ proof_cb (void *cls,
{
rctx->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
rctx->response
- = TMH_RESPONSE_make_internal_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
- "Fail to elaborate response.");
+ = TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
+ "Fail to elaborate response.");
return;
}
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index d6b32602..a3fca0fe 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -55,7 +55,7 @@ typedef void
* Does a GET /refund.
*
* @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
* @param order_id order id used to perform the lookup
* @param cb callback which will work the response gotten from the backend
* @param cb_cls closure to pass to the callback
@@ -63,7 +63,7 @@ typedef void
*/
struct TALER_MERCHANT_RefundLookupOperation *
TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const char *order_id,
const char *instance,
TALER_MERCHANT_RefundLookupCallback cb,
@@ -103,7 +103,7 @@ typedef void
* Increase the refund associated to a order
*
* @param ctx the CURL context used to connect to the backend
- * @param backend_uri backend's base URL, including final "/"
+ * @param backend_url backend's base URL, including final "/"
* @param order_id id of the order whose refund is to be increased
* @param refund amount to which increase the refund
* @param reason human-readable reason justifying the refund
@@ -113,7 +113,7 @@ typedef void
*/
struct TALER_MERCHANT_RefundIncreaseOperation *
TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const char *order_id,
const struct TALER_Amount *refund,
const char *reason,
@@ -165,7 +165,7 @@ typedef void
* PUT an order to the backend and receives the related proposal.
*
* @param ctx execution context
- * @param backend_uri URI of the backend
+ * @param backend_url URL of the backend
* @param order basic information about this purchase, to be extended by the
* backend
* @param proposal_cb the callback to call when a reply for this request is available
@@ -174,7 +174,7 @@ typedef void
*/
struct TALER_MERCHANT_ProposalOperation *
TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const json_t *order,
TALER_MERCHANT_ProposalCallback proposal_cb,
void *proposal_cb_cls);
@@ -215,7 +215,7 @@ typedef void
* retrieve a proposal data by providing its transaction id.
*
* @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
* @param transaction_id transaction id used to perform the lookup
* @param plo_cb callback which will work the response gotten from the backend
* @param plo_cb_cls closure to pass to @a history_cb
@@ -223,7 +223,7 @@ typedef void
*/
struct TALER_MERCHANT_ProposalLookupOperation *
TALER_MERCHANT_proposal_lookup (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const char *transaction_id,
const char *instance,
TALER_MERCHANT_ProposalLookupOperationCallback plo_cb,
@@ -249,7 +249,7 @@ TALER_MERCHANT_proposal_lookup_cancel (struct TALER_MERCHANT_ProposalLookupOpera
* or backends (API for frontends). The difference is that for the
* frontend API, we need the private keys of the coins, while for
* the backend API we need the public keys and signatures received
- * from the wallet. Also, the frontend returns a redirect URI on
+ * from the wallet. Also, the frontend returns a redirect URL on
* success, while the backend just returns a success status code.
*/
struct TALER_MERCHANT_Pay;
@@ -310,6 +310,11 @@ struct TALER_MERCHANT_PayCoin
*/
struct TALER_Amount amount_without_fee;
+ /**
+ * URL of the exchange that issued @e coin_priv.
+ */
+ const char *exchange_url;
+
};
@@ -317,7 +322,7 @@ struct TALER_MERCHANT_PayCoin
* Pay a merchant. API for wallets that have the coin's private keys.
*
* @param ctx execution context
- * @param merchant_uri base URI of the merchant
+ * @param merchant_url base URL of the merchant
* @param instance which merchant instance will receive this payment
* @param h_wire hash of the merchant’s account details
* @param h_contract hash of the contact of the merchant with the customer
@@ -329,7 +334,6 @@ struct TALER_MERCHANT_PayCoin
* @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
* @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 pay_deadline maximum time limit to pay for this contract
- * @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.
@@ -339,7 +343,7 @@ struct TALER_MERCHANT_PayCoin
*/
struct TALER_MERCHANT_Pay *
TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
- const char *merchant_uri,
+ const char *merchant_url,
const char *instance,
const struct GNUNET_HashCode *h_contract,
const struct TALER_Amount *amount,
@@ -350,7 +354,6 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
struct GNUNET_TIME_Absolute refund_deadline,
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,
@@ -399,6 +402,11 @@ struct TALER_MERCHANT_PaidCoin
*/
struct TALER_Amount amount_without_fee;
+ /**
+ * What is the URL of the exchange that issued @a coin_pub?
+ */
+ const char *exchange_url;
+
};
@@ -409,7 +417,7 @@ struct TALER_MERCHANT_PaidCoin
* in the type of @a coins compared to #TALER_MERCHANT_pay().
*
* @param ctx execution context
- * @param merchant_uri base URI of the merchant
+ * @param merchant_url base URL of the merchant
* @param instance which merchant instance will receive this payment
* @param h_contract hash of the contact of the merchant with the customer
* @param amount total value of the contract to be paid to the merchant
@@ -420,7 +428,6 @@ struct TALER_MERCHANT_PaidCoin
* @param pay_deadline maximum time limit to pay for this contract
* @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
* @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.
@@ -430,10 +437,9 @@ struct TALER_MERCHANT_PaidCoin
*/
struct TALER_MERCHANT_Pay *
TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx,
- const char *merchant_uri,
+ const char *merchant_url,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *order_id,
- const char *exchange_uri,
unsigned int num_coins,
const struct TALER_MERCHANT_PaidCoin *coins,
TALER_MERCHANT_PayCallback pay_cb,
@@ -517,7 +523,7 @@ typedef void
* Request backend to return deposits associated with a given wtid.
*
* @param ctx execution context
- * @param backend_uri base URI of the backend
+ * @param backend_url base URL of the backend
* @param instance which merchant instance is going to be tracked
* @param wire_method wire method used for the wire transfer
* @param wtid base32 string indicating a wtid
@@ -528,11 +534,11 @@ typedef void
*/
struct TALER_MERCHANT_TrackTransferHandle *
TALER_MERCHANT_track_transfer (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const char *instance,
const char *wire_method,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const char *exchange_uri,
+ const char *exchange_url,
TALER_MERCHANT_TrackTransferCallback track_transfer_cb,
void *track_transfer_cb_cls);
@@ -601,7 +607,7 @@ typedef void
* Request backend to return deposits associated with a given wtid.
*
* @param ctx execution context
- * @param backend_uri base URI of the backend
+ * @param backend_url base URL of the backend
* @param instance which merchant instance is going to be tracked
* @param transaction_id which transaction should we trace
* @param track_transaction_cb the callback to call when a reply for this request is available
@@ -610,7 +616,7 @@ typedef void
*/
struct TALER_MERCHANT_TrackTransactionHandle *
TALER_MERCHANT_track_transaction (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const char *instance,
const char *order_id,
TALER_MERCHANT_TrackTransactionCallback track_transaction_cb,
@@ -650,7 +656,7 @@ typedef void
* Issue a /history request to the backend.
*
* @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
* @param instance which merchant instance is performing this call
* @param start return @a delta records starting from position @a start
* @param delta return @a delta records starting from position @a start
@@ -661,7 +667,7 @@ typedef void
*/
struct TALER_MERCHANT_HistoryOperation *
TALER_MERCHANT_history (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const char *instance,
unsigned int start,
unsigned int delta,
@@ -707,7 +713,7 @@ typedef void
* Issue a /tip-enable request to the backend. Informs the backend
* that a reserve is now available for tipping. Note that the
* respective @a reserve_priv must also be bound to one or more
- * instances (together with the URI of the exchange) via the backend's
+ * instances (together with the URL of the exchange) via the backend's
* configuration file before it can be used. Usually, the process
* is that one first configures an exchange and a @a reserve_priv for
* an instance, and then enables (or re-enables) the reserve by
@@ -715,7 +721,7 @@ typedef void
* this API.
*
* @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
* @param amount amount that was credited to the reserve
* @param expiration when will the reserve expire
* @param reserve_priv private key of the reserve
@@ -726,7 +732,7 @@ typedef void
*/
struct TALER_MERCHANT_TipEnableOperation *
TALER_MERCHANT_tip_enable (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const struct TALER_Amount *amount,
struct GNUNET_TIME_Absolute expiration,
const struct TALER_ReservePrivateKeyP *reserve_priv,
@@ -762,7 +768,7 @@ struct TALER_MERCHANT_TipAuthorizeOperation;
* @param ec taler-specific error code
* @param tip_id which tip ID should be used to pickup the tip
* @param tip_expiration when does the tip expire (needs to be picked up before this time)
- * @param exchange_uri at what exchange can the tip be picked up
+ * @param exchange_url at what exchange can the tip be picked up
*/
typedef void
(*TALER_MERCHANT_TipAuthorizeCallback) (void *cls,
@@ -770,7 +776,7 @@ typedef void
enum TALER_ErrorCode ec,
const struct GNUNET_HashCode *tip_id,
struct GNUNET_TIME_Absolute tip_expiration,
- const char *exchange_uri);
+ const char *exchange_url);
/**
@@ -845,7 +851,7 @@ typedef void
* that a customer wants to pick up a tip.
*
* @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
* @param tip_id unique identifier for the tip
* @param num_planches number of planchets provided in @a planchets
* @param planchets array of planchets to be signed into existence for the tip
@@ -855,7 +861,7 @@ typedef void
*/
struct TALER_MERCHANT_TipPickupOperation *
TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
- const char *backend_uri,
+ const char *backend_url,
const struct GNUNET_HashCode *tip_id,
unsigned int num_planchets,
struct TALER_PlanchetDetail *planchets,
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
index a4800617..bfb1ae85 100644
--- a/src/lib/merchant_api_pay.c
+++ b/src/lib/merchant_api_pay.c
@@ -265,7 +265,6 @@ handle_pay_finished (void *cls,
* @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 pay_deadline maximum time limit to pay for this contract
* @param h_wire hash of the merchant’s account details
- * @param exchange_uri URI of the exchange that the coins belong to
* @param order_id order id of the proposal being paid
* @param num_coins number of coins used to pay
* @param coins array of coins we use to pay
@@ -286,14 +285,12 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
struct GNUNET_TIME_Absolute refund_deadline,
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,
void *pay_cb_cls)
{
- unsigned int i;
struct TALER_DepositRequestPS dr;
struct TALER_MERCHANT_PaidCoin pc[num_coins];
@@ -316,7 +313,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
dr.merchant = *merchant_pub;
- for (i=0;i<num_coins;i++)
+ for (unsigned int i=0;i<num_coins;i++)
{
const struct TALER_MERCHANT_PayCoin *coin = &coins[i];
struct TALER_MERCHANT_PaidCoin *p = &pc[i];
@@ -355,12 +352,12 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
p->coin_pub = dr.coin_pub;
p->amount_with_fee = coin->amount_with_fee;
p->amount_without_fee = coin->amount_without_fee;
+ p->exchange_url = coin->exchange_url;
}
return TALER_MERCHANT_pay_frontend (ctx,
merchant_uri,
merchant_pub,
order_id,
- exchange_uri,
num_coins,
pc,
pay_cb,
@@ -377,7 +374,6 @@ 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 merchant_pub public key of the merchant
- * @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 pay_cb the callback to call when a reply for this request is available
@@ -389,7 +385,6 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx,
const char *merchant_uri,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *order_id,
- const char *exchange_uri,
unsigned int num_coins,
const struct TALER_MERCHANT_PaidCoin *coins,
TALER_MERCHANT_PayCallback pay_cb,
@@ -449,11 +444,12 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx,
}
/* create JSON for this coin */
- j_coin = json_pack ("{s:o, s:o," /* f/coin_pub */
- " s:o, s:o," /* denom_pub / ub_sig */
- " s:o}", /* coin_sig */
- "f", TALER_JSON_from_amount (&pc->amount_with_fee),
+ j_coin = json_pack ("{s:o, s:o," /* contribution/coin_pub */
+ " s:s, s:o," /* exchange_url / denom_pub */
+ " s:o, s:o}", /* ub_sig / coin_sig */
+ "contribution", TALER_JSON_from_amount (&pc->amount_with_fee),
"coin_pub", GNUNET_JSON_from_data_auto (&pc->coin_pub),
+ "exchange_url", pc->exchange_url,
"denom_pub", GNUNET_JSON_from_rsa_public_key (pc->denom_pub.rsa_public_key),
"ub_sig", GNUNET_JSON_from_rsa_signature (pc->denom_sig.rsa_signature),
"coin_sig", GNUNET_JSON_from_data_auto (&pc->coin_sig)
@@ -469,12 +465,10 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx,
}
pay_obj = json_pack ("{"
- " s:s," /* exchange */
" s:o," /* coins */
" s:s," /* order_id */
" s:o," /* merchant_pub */
"}",
- "exchange", exchange_uri,
"coins", j_coins,
"order_id", order_id,
"merchant_pub", GNUNET_JSON_from_data_auto (merchant_pub));
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index 769421b5..941f46b1 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -2696,12 +2696,14 @@ interpreter_run (void *cls)
icoin->denom_pub = coin_ref->details.reserve_withdraw.pk->key;
icoin->denom_sig = coin_ref->details.reserve_withdraw.sig;
icoin->denom_value = coin_ref->details.reserve_withdraw.pk->value;
+ icoin->exchange_url = EXCHANGE_URL;
break;
case OC_TIP_PICKUP:
icoin->coin_priv = coin_ref->details.tip_pickup.psa[ci].coin_priv;
icoin->denom_pub = coin_ref->details.tip_pickup.dks[ci]->key;
icoin->denom_sig = coin_ref->details.tip_pickup.sigs[ci];
icoin->denom_value = coin_ref->details.tip_pickup.dks[ci]->value;
+ icoin->exchange_url = EXCHANGE_URL;
break;
default:
GNUNET_assert (0);
@@ -2729,7 +2731,6 @@ interpreter_run (void *cls)
refund_deadline,
pay_deadline,
&h_wire,
- EXCHANGE_URL,
order_id,
npc /* num_coins */,
pc /* coins */,
diff --git a/src/merchant-tools/taler-merchant-generate-payments.c b/src/merchant-tools/taler-merchant-generate-payments.c
index 4ba257fe..887b9db4 100644
--- a/src/merchant-tools/taler-merchant-generate-payments.c
+++ b/src/merchant-tools/taler-merchant-generate-payments.c
@@ -1167,6 +1167,7 @@ interpreter_run (void *cls)
pc.denom_pub = coin_ref->details.reserve_withdraw.pk->key;
pc.denom_sig = coin_ref->details.reserve_withdraw.sig;
pc.denom_value = coin_ref->details.reserve_withdraw.pk->value;
+ pc.exchange_url = exchange_url;
break;
default:
GNUNET_assert (0);
@@ -1209,7 +1210,6 @@ interpreter_run (void *cls)
refund_deadline,
pay_deadline,
&h_wire,
- exchange_url,
order_id,
1 /* num_coins */,
&pc /* coins */,