summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_post-orders-ID-pay.c')
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c2438
1 files changed, 1174 insertions, 1264 deletions
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 957bfdfa..14edfd55 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2022 Taler Systems SA
+ (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -29,7 +29,6 @@
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
-#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
@@ -60,6 +59,63 @@ struct PayContext;
/**
+ * Different phases of processing the /pay request.
+ */
+enum PayPhase
+{
+ /**
+ * Initial phase where the request is parsed.
+ */
+ PP_INIT = 0,
+
+ /**
+ * Check database state for the given order.
+ */
+ PP_CHECK_CONTRACT,
+
+ /**
+ * Contract has been paid.
+ */
+ PP_CONTRACT_PAID,
+
+ /**
+ * Execute payment transaction.
+ */
+ PP_PAY_TRANSACTION,
+
+ /**
+ * Notify other processes about successful payment.
+ */
+ PP_PAYMENT_NOTIFICATION,
+
+ /**
+ * Create final success response.
+ */
+ PP_SUCCESS_RESPONSE,
+
+ /**
+ * Perform batch deposits with exchange(s).
+ */
+ PP_BATCH_DEPOSITS,
+
+ /**
+ * Return response in payment context.
+ */
+ PP_RETURN_RESPONSE,
+
+ /**
+ * Return #MHD_YES to end processing.
+ */
+ PP_END_YES,
+
+ /**
+ * Return #MHD_NO to end processing.
+ */
+ PP_END_NO
+};
+
+
+/**
* Information kept during a pay request for each coin.
*/
struct DepositConfirmation
@@ -71,12 +127,6 @@ struct DepositConfirmation
struct PayContext *pc;
/**
- * Handle to the deposit operation we are performing for
- * this coin, NULL after the operation is done.
- */
- struct TALER_EXCHANGE_DepositHandle *dh;
-
- /**
* URL of the exchange that issued this coin.
*/
char *exchange_url;
@@ -97,11 +147,6 @@ struct DepositConfirmation
struct TALER_Amount refund_fee;
/**
- * Wire fee charged by the exchange of this coin.
- */
- struct TALER_Amount wire_fee;
-
- /**
* If a minimum age was required (i. e. pc->minimum_age is large enough),
* this is the signature of the minimum age (as a single uint8_t), using the
* private key to the corresponding age group. Might be all zeroes for no
@@ -180,7 +225,7 @@ struct ExchangeGroup
* the exchange used for this transaction; NULL if no operation is
* pending.
*/
- struct TMH_EXCHANGES_FindOperation *fo;
+ struct TMH_EXCHANGES_KeysOperation *fo;
/**
* URL of the exchange that issued this coin. Aliases
@@ -189,6 +234,12 @@ struct ExchangeGroup
const char *exchange_url;
/**
+ * Wire fee that applies to this exchange for the
+ * given payment context's wire method.
+ */
+ struct TALER_Amount wire_fee;
+
+ /**
* true if we already tried a forced /keys download.
*/
bool tried_force_keys;
@@ -234,7 +285,7 @@ struct PayContext
/**
* What wire method (of the @e mi) was selected by the wallet?
- * Set in #parse_pay().
+ * Set in #phase_parse_pay().
*/
struct TMH_WireMethod *wm;
@@ -297,30 +348,11 @@ struct PayContext
* Note that IF the total fee of the exchange is higher, that is
* acceptable to the merchant if the customer is willing to
* pay the difference
- * (i.e. amount - max_fee <= actual-amount - actual-fee).
+ * (i.e. amount - max_fee <= actual_amount - actual_fee).
*/
struct TALER_Amount max_fee;
/**
- * Maximum wire 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
- * pay the amorized difference. Wire fees are charged over an
- * aggregate of several translations, hence unlike the deposit
- * fees, they are amortized over several customer's transactions.
- * The contract specifies under @e wire_fee_amortization how many
- * customer's transactions he expects the wire fees to be amortized
- * over on average. Thus, if the wire fees are larger than
- * @e max_wire_fee, each customer is expected to contribute
- * $\frac{actual-wire-fee - max_wire_fee}{wire_fee_amortization}$.
- * The customer's contribution may be further reduced by the
- * difference between @e max_fee and the sum of the deposit fees.
- *
- * Default is that the merchant is unwilling to pay any wire fees.
- */
- struct TALER_Amount max_wire_fee;
-
- /**
* Amount from @e root. This is the amount the merchant expects
* to make, minus @e max_fee.
*/
@@ -368,13 +400,14 @@ struct PayContext
struct GNUNET_TIME_Timestamp pay_deadline;
/**
- * Number of transactions that the wire fees are expected to be
- * amortized over. Never zero, defaults (conservateively) to 1.
- * May be higher if merchants expect many small transactions to
- * be aggregated and thus wire fees to be reasonably amortized
- * due to aggregation.
+ * Set to the POS key, if applicable for this order.
*/
- uint32_t wire_fee_amortization;
+ char *pos_key;
+
+ /**
+ * Algorithm chosen for generating the confirmation code.
+ */
+ enum TALER_MerchantConfirmationAlgorithm pos_alg;
/**
* Minimum age required for this purchase.
@@ -385,7 +418,7 @@ struct PayContext
* Number of coins this payment is made of. Length
* of the @e dc array.
*/
- unsigned int coins_cnt;
+ size_t coins_cnt;
/**
* Number of exchanges involved in the payment. Length
@@ -416,6 +449,11 @@ struct PayContext
unsigned int response_code;
/**
+ * Payment processing phase we are in.
+ */
+ enum PayPhase phase;
+
+ /**
* #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
@@ -423,68 +461,17 @@ struct PayContext
*/
enum GNUNET_GenericReturnValue suspended;
-};
-
-
-/**
- * Active KYC operation with an exchange.
- */
-struct KycContext
-{
- /**
- * Kept in a DLL.
- */
- struct KycContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct KycContext *prev;
-
/**
- * Looking for the exchange.
+ * Set to true if the deposit currency of a coin
+ * does not match the contract currency.
*/
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Exchange this is about.
- */
- char *exchange_url;
+ bool deposit_currency_mismatch;
/**
- * Merchant instance this is for.
+ * Set to true if the database contains a (bogus)
+ * refund for a different currency.
*/
- struct TMH_MerchantInstance *mi;
-
- /**
- * Wire method we are checking the status of.
- */
- struct TMH_WireMethod *wm;
-
- /**
- * Handle for the GET /deposits operation.
- */
- struct TALER_EXCHANGE_DepositGetHandle *dg;
-
- /**
- * Contract we are looking up.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Coin we are looking up.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Initial DB timestamp.
- */
- struct GNUNET_TIME_Timestamp kyc_timestamp;
-
- /**
- * Initial KYC status.
- */
- bool kyc_ok;
+ bool refund_currency_mismatch;
};
@@ -499,108 +486,14 @@ static struct PayContext *pc_head;
*/
static struct PayContext *pc_tail;
-/**
- * Head of active KYC context DLL.
- */
-static struct KycContext *kc_head;
-
-/**
- * Tail of active KYC context DLL.
- */
-static struct KycContext *kc_tail;
-
-
-/**
- * Free resources used by @a kc.
- *
- * @param[in] kc object to free
- */
-static void
-destroy_kc (struct KycContext *kc)
-{
- if (NULL != kc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (kc->fo);
- kc->fo = NULL;
- }
- if (NULL != kc->dg)
- {
- TALER_EXCHANGE_deposits_get_cancel (kc->dg);
- kc->dg = NULL;
- }
- TMH_instance_decref (kc->mi);
- kc->mi = NULL;
- GNUNET_free (kc->exchange_url);
- GNUNET_CONTAINER_DLL_remove (kc_head,
- kc_tail,
- kc);
- GNUNET_free (kc);
-}
-
-
-/**
- * Compute the timeout for a /pay request based on the number of coins
- * involved.
- *
- * @param num_coins number of coins
- * @returns timeout for the /pay request
- */
-static struct GNUNET_TIME_Relative
-get_pay_timeout (unsigned int num_coins)
-{
- struct GNUNET_TIME_Relative t;
-
- /* FIXME: Do some benchmarking to come up with a better timeout.
- * We've increased this value so the wallet integration test passes again
- * on my (Florian) machine.
- */
- t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
- 15 * (1 + (num_coins / 5)));
-
- return t;
-}
-
-
-/**
- * Abort all pending /deposit operations.
- *
- * @param pc pay context to abort
- */
-static void
-abort_active_deposits (struct PayContext *pc)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Aborting pending /deposit operations\n");
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
- {
- struct DepositConfirmation *dci = &pc->dc[i];
-
- if (NULL != dci->dh)
- {
- TALER_EXCHANGE_deposit_cancel (dci->dh);
- dci->dh = NULL;
- }
- }
-}
-
void
TMH_force_pc_resume ()
{
- struct KycContext *kc;
-
- while (NULL != (kc = kc_head))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Aborting KYC check at %s\n",
- kc->exchange_url);
- destroy_kc (kc);
- }
for (struct PayContext *pc = pc_head;
NULL != pc;
pc = pc->next)
{
- abort_active_deposits (pc);
if (NULL != pc->timeout_task)
{
GNUNET_SCHEDULER_cancel (pc->timeout_task);
@@ -616,6 +509,21 @@ TMH_force_pc_resume ()
/**
+ * Resume payment processing.
+ *
+ * @param[in,out] pc payment process to resume
+ */
+static void
+pay_resume (struct PayContext *pc)
+{
+ GNUNET_assert (GNUNET_YES == pc->suspended);
+ pc->suspended = GNUNET_NO;
+ MHD_resume_connection (pc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
+/**
* Resume the given pay context and send the given response.
* Stores the response in the @a pc and signals MHD to resume
* the connection. Also ensures MHD runs immediately.
@@ -629,21 +537,36 @@ resume_pay_with_response (struct PayContext *pc,
unsigned int response_code,
struct MHD_Response *response)
{
- abort_active_deposits (pc);
pc->response_code = response_code;
pc->response = response;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Resuming /pay handling. HTTP status for our reply is %u.\n",
response_code);
+ for (unsigned int i = 0; i<pc->num_exchanges; i++)
+ {
+ struct ExchangeGroup *eg = pc->egs[i];
+
+ if (NULL != eg->fo)
+ {
+ TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
+ eg->fo = NULL;
+ pc->pending_at_eg--;
+ }
+ if (NULL != eg->bdh)
+ {
+ TALER_EXCHANGE_batch_deposit_cancel (eg->bdh);
+ eg->bdh = NULL;
+ pc->pending_at_eg--;
+ }
+ }
+ GNUNET_assert (0 == pc->pending_at_eg);
if (NULL != pc->timeout_task)
{
GNUNET_SCHEDULER_cancel (pc->timeout_task);
pc->timeout_task = NULL;
}
- GNUNET_assert (GNUNET_YES == pc->suspended);
- pc->suspended = GNUNET_NO;
- MHD_resume_connection (pc->connection);
- TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+ pc->phase = PP_RETURN_RESPONSE;
+ pay_resume (pc);
}
@@ -651,292 +574,148 @@ resume_pay_with_response (struct PayContext *pc,
* Resume payment processing with an error.
*
* @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
resume_pay_with_error (struct PayContext *pc,
- unsigned int http_status,
enum TALER_ErrorCode ec,
const char *msg)
{
- resume_pay_with_response (pc,
- http_status,
- TALER_MHD_make_error (ec,
- msg));
-}
-
-
-/**
- * Custom cleanup routine for a `struct PayContext`.
- *
- * @param cls the `struct PayContext` to clean up.
- */
-static void
-pay_context_cleanup (void *cls)
-{
- struct PayContext *pc = cls;
-
- if (NULL != pc->timeout_task)
- {
- GNUNET_SCHEDULER_cancel (pc->timeout_task);
- pc->timeout_task = NULL;
- }
- if (NULL != pc->contract_terms)
- {
- json_decref (pc->contract_terms);
- pc->contract_terms = NULL;
- }
- abort_active_deposits (pc);
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
- {
- struct DepositConfirmation *dc = &pc->dc[i];
-
- TALER_denom_sig_free (&dc->cdd.denom_sig);
- GNUNET_free (dc->exchange_url);
- }
- GNUNET_free (pc->dc);
- for (unsigned int i = 0; i<pc->num_exchanges; i++)
- {
- struct ExchangeGroup *eg = pc->egs[i];
-
- if (NULL != eg->fo)
- TMH_EXCHANGES_find_exchange_cancel (eg->fo);
- GNUNET_free (eg);
- }
- GNUNET_free (pc->egs);
- if (NULL != pc->response)
- {
- MHD_destroy_response (pc->response);
- pc->response = NULL;
- }
- GNUNET_free (pc->fulfillment_url);
- GNUNET_free (pc->session_id);
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- GNUNET_free (pc);
+ resume_pay_with_response (
+ pc,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_MHD_make_error (ec,
+ msg));
}
/**
- * Execute the DB transaction. If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
- *
- * @param pc payment context to transact
- */
-static void
-execute_pay_transaction (struct PayContext *pc);
-
-
-/**
- * Function called with detailed wire transfer data.
+ * Conclude payment processing for @a pc with the
+ * given @a res MHD status code.
*
- * @param cls a `struct KycContext *`
- * @param dr HTTP response data
+ * @param[in,out] pc payment context for final state transition
+ * @param res MHD return code to end with
*/
static void
-deposit_get_callback (
- void *cls,
- const struct TALER_EXCHANGE_GetDepositResponse *dr)
+pay_end (struct PayContext *pc,
+ MHD_RESULT res)
{
- struct KycContext *kc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp now;
-
- kc->dg = NULL;
- now = GNUNET_TIME_timestamp_get ();
- switch (dr->hr.http_status)
- {
- case MHD_HTTP_OK:
- qs = TMH_db->account_kyc_set_status (
- TMH_db->cls,
- kc->mi->settings.id,
- &kc->wm->h_wire,
- kc->exchange_url,
- 0LL,
- NULL, /* no signature */
- NULL, /* no signature */
- now,
- true);
- GNUNET_break (qs > 0);
- break;
- case MHD_HTTP_ACCEPTED:
- qs = TMH_db->account_kyc_set_status (
- TMH_db->cls,
- kc->mi->settings.id,
- &kc->wm->h_wire,
- kc->exchange_url,
- dr->details.accepted.requirement_row,
- NULL, /* no signature */
- NULL, /* no signature */
- now,
- dr->details.accepted.kyc_ok);
- GNUNET_break (qs > 0);
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "KYC check failed at %s with unexpected status %u\n",
- kc->exchange_url,
- dr->hr.http_status);
- }
- destroy_kc (kc);
+ pc->phase = (MHD_YES == res)
+ ? PP_END_YES
+ : PP_END_NO;
}
/**
- * Function called with the result of our exchange lookup.
+ * Return response stored in @a pc.
*
- * @param cls the `struct KycContext`
- * @param hr HTTP response details
- * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- * NULL if not available
- * @param exchange_trusted true if this exchange is
- * trusted by config
+ * @param[in,out] pc payment context we are processing
*/
static void
-process_kyc_with_exchange (
- void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *exchange_handle,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+phase_return_response (struct PayContext *pc)
{
- struct KycContext *kc = cls;
-
- kc->fo = NULL;
- if (NULL == exchange_handle)
- {
- destroy_kc (kc);
- return;
- }
- kc->dg = TALER_EXCHANGE_deposits_get (exchange_handle,
- &kc->mi->merchant_priv,
- &kc->wm->h_wire,
- &kc->h_contract_terms,
- &kc->coin_pub,
- &deposit_get_callback,
- kc);
- if (NULL == kc->dg)
+ GNUNET_assert (0 != pc->response_code);
+ /* We are *done* processing the request, just queue the response (!) */
+ if (UINT_MAX == pc->response_code)
{
GNUNET_break (0);
- destroy_kc (kc);
+ pay_end (pc,
+ MHD_NO); /* hard error */
+ return;
}
+ pay_end (pc,
+ MHD_queue_response (pc->connection,
+ pc->response_code,
+ pc->response));
}
/**
- * Function called from ``account_kyc_get_status``
- * with KYC status information for this merchant.
- *
- * @param cls a `struct KycContext *`
- * @param h_wire hash of the wire account
- * @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown
- * @param payto_uri payto:// URI of the merchant's bank account
- * @param exchange_url base URL of the exchange for which this is a status
- * @param last_check when did we last get an update on our KYC status from the exchange
- * @param kyc_ok true if we satisfied the KYC requirements
- */
-static void
-kyc_cb (
- void *cls,
- const struct TALER_MerchantWireHashP *h_wire,
- uint64_t exchange_kyc_serial,
- const char *payto_uri,
- const char *exchange_url,
- struct GNUNET_TIME_Timestamp last_check,
- bool kyc_ok)
-{
- struct KycContext *kc = cls;
-
- kc->kyc_timestamp = last_check;
- kc->kyc_ok = kyc_ok;
-}
-
-
-/**
- * Check for our KYC status at @a exchange_url for the
- * payment of @a pc. First checks if we already have a
- * positive result from the exchange, and if not checks
- * with the exchange.
+ * Do database transaction for a completed batch deposit.
*
- * @param pc payment context to use as starting point
- * @param eg exchange group of the exchange we are triggering on
+ * @param eg group that completed
+ * @param dr response from the server
+ * @return transaction status
*/
-static void
-check_kyc (struct PayContext *pc,
- const struct ExchangeGroup *eg)
+static enum GNUNET_DB_QueryStatus
+batch_deposit_transaction (const struct ExchangeGroup *eg,
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
+ const struct PayContext *pc = eg->pc;
enum GNUNET_DB_QueryStatus qs;
- struct KycContext *kc;
+ struct TALER_Amount total_without_fees;
+ uint64_t b_dep_serial;
+ uint32_t off = 0;
- kc = GNUNET_new (struct KycContext);
- qs = TMH_db->account_kyc_get_status (TMH_db->cls,
- pc->hc->instance->settings.id,
- &pc->wm->h_wire,
- eg->exchange_url,
- &kyc_cb,
- kc);
- if (qs < 0)
- {
- GNUNET_break (0);
- GNUNET_free (kc);
- return;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (kc->kyc_ok)
- {
- GNUNET_free (kc);
- return; /* we are done */
- }
- if (GNUNET_TIME_relative_cmp (
- GNUNET_TIME_absolute_get_duration (
- kc->kyc_timestamp.abs_time),
- <,
- KYC_RETRY_FREQUENCY))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not re-checking KYC status at `%s', as we already recently asked\n",
- eg->exchange_url);
- GNUNET_free (kc);
- return;
- }
- }
- kc->mi = pc->hc->instance;
- kc->mi->rc++;
- kc->wm = pc->wm;
- kc->exchange_url = GNUNET_strdup (eg->exchange_url);
- kc->h_contract_terms = pc->h_contract_terms;
- /* find one of the coins of the batch */
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &total_without_fees));
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
+ struct TALER_Amount amount_without_fees;
+ /* might want to group deposits by batch more explicitly ... */
if (0 != strcmp (eg->exchange_url,
- pc->dc[i].exchange_url))
+ dc->exchange_url))
continue;
- kc->coin_pub = dc->cdd.coin_pub;
- break;
- }
- GNUNET_CONTAINER_DLL_insert (kc_head,
- kc_tail,
- kc);
- kc->fo = TMH_EXCHANGES_find_exchange (kc->exchange_url,
- NULL,
- GNUNET_NO,
- &process_kyc_with_exchange,
- kc);
- if (NULL == kc->fo)
+ if (dc->found_in_db)
+ continue;
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&amount_without_fees,
+ &dc->cdd.amount,
+ &dc->deposit_fee));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&total_without_fees,
+ &total_without_fees,
+ &amount_without_fees));
+ }
+ qs = TMH_db->insert_deposit_confirmation (
+ TMH_db->cls,
+ pc->hc->instance->settings.id,
+ dr->details.ok.deposit_timestamp,
+ &pc->h_contract_terms,
+ eg->exchange_url,
+ pc->wire_transfer_deadline,
+ &total_without_fees,
+ &eg->wire_fee,
+ &pc->wm->h_wire,
+ dr->details.ok.exchange_sig,
+ dr->details.ok.exchange_pub,
+ &b_dep_serial);
+ if (qs <= 0)
+ return qs; /* Entire batch already known or failure, we're done */
+
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
- GNUNET_break (0);
- destroy_kc (kc);
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ /* might want to group deposits by batch more explicitly ... */
+ if (0 != strcmp (eg->exchange_url,
+ dc->exchange_url))
+ continue;
+ if (dc->found_in_db)
+ continue;
+ /* NOTE: We might want to check if the order was fully paid concurrently
+ by some other wallet here, and if so, issue an auto-refund. Right now,
+ it is possible to over-pay if two wallets literally make a concurrent
+ payment, as the earlier check for 'paid' is not in the same transaction
+ scope as this 'insert' operation. */
+ qs = TMH_db->insert_deposit (
+ TMH_db->cls,
+ off++, /* might want to group deposits by batch more explicitly ... */
+ b_dep_serial,
+ &dc->cdd.coin_pub,
+ &dc->cdd.coin_sig,
+ &dc->cdd.amount,
+ &dc->deposit_fee,
+ &dc->refund_fee);
+ if (qs < 0)
+ return qs;
+ GNUNET_break (qs > 0);
}
+ return qs;
}
@@ -963,8 +742,6 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
pc->hc->instance->settings.id);
for (unsigned int r = 0; r<MAX_RETRIES; r++)
{
- unsigned int j = 0;
-
TMH_db->preflight (TMH_db->cls);
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
@@ -979,51 +756,19 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
TMH_pack_exchange_reply (&dr->hr)));
return;
}
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ qs = batch_deposit_transaction (eg,
+ dr);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- struct DepositConfirmation *dc = &pc->dc[i];
-
- if (0 != strcmp (eg->exchange_url,
- pc->dc[i].exchange_url))
- continue;
- if (dc->found_in_db)
- continue;
- /* NOTE: We might want to check if the order was fully paid concurrently
- by some other wallet here, and if so, issue an auto-refund. Right now,
- it is possible to over-pay if two wallets literally make a concurrent
- payment, as the earlier check for 'paid' is not in the same transaction
- scope as this 'insert' operation. */
- GNUNET_assert (j < dr->details.success.num_signatures);
- qs = TMH_db->insert_deposit (
- TMH_db->cls,
- pc->hc->instance->settings.id,
- dr->details.success.deposit_timestamp,
- &pc->h_contract_terms,
- &dc->cdd.coin_pub,
- dc->exchange_url,
- &dc->cdd.amount,
- &dc->deposit_fee,
- &dc->refund_fee,
- &dc->wire_fee,
- &pc->wm->h_wire,
- &dr->details.success.exchange_sigs[j++],
- dr->details.success.exchange_pub);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- TMH_db->rollback (TMH_db->cls);
- break;
- }
- if (0 > qs)
- {
- /* 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_GENERIC_DB_STORE_FAILED,
- "insert_deposit");
- return;
- }
+ TMH_db->rollback (TMH_db->cls);
+ continue;
+ }
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "batch_deposit_transaction");
}
qs = TMH_db->commit (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -1035,23 +780,21 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
{
GNUNET_break (0);
resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
"insert_deposit");
}
break; /* DB transaction succeeded */
- } /* FOR DB retries */
+ }
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
"insert_deposit");
return;
}
/* Transaction is done, mark affected coins as complete as well. */
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1063,8 +806,6 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */
pc->pending--;
}
- check_kyc (pc,
- eg);
}
@@ -1083,18 +824,21 @@ batch_deposit_cb (
struct PayContext *pc = eg->pc;
eg->bdh = NULL;
+ pc->pending_at_eg--;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Batch deposit completed with status %u\n",
dr->hr.http_status);
GNUNET_assert (GNUNET_YES == pc->suspended);
- pc->pending_at_eg--;
switch (dr->hr.http_status)
{
case MHD_HTTP_OK:
handle_batch_deposit_ok (eg,
dr);
if (0 == pc->pending_at_eg)
- execute_pay_transaction (eg->pc);
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
+ }
return;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -1137,8 +881,8 @@ batch_deposit_cb (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS),
TMH_pack_exchange_reply (&dr->hr),
- GNUNET_JSON_pack_data_auto ("exchange_url",
- &eg->exchange_url)));
+ GNUNET_JSON_pack_string ("exchange_url",
+ eg->exchange_url)));
return;
}
resume_pay_with_response (
@@ -1148,92 +892,102 @@ batch_deposit_cb (
TALER_JSON_pack_ec (
TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS),
TMH_pack_exchange_reply (&dr->hr),
- GNUNET_JSON_pack_data_auto ("exchange_url",
- &eg->exchange_url)));
+ GNUNET_JSON_pack_string ("exchange_url",
+ eg->exchange_url)));
return;
} /* end switch */
}
/**
- * Function called with the result of our exchange lookup.
+ * Force re-downloading keys for @a eg.
+ *
+ * @param[in,out] eg group to re-download keys for
+ */
+static void
+force_keys (struct ExchangeGroup *eg);
+
+
+/**
+ * Function called with the result of our exchange keys lookup.
*
* @param cls the `struct ExchangeGroup`
- * @param hr HTTP response details
- * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- * NULL if not available
- * @param exchange_trusted true if this exchange is
- * trusted by config
+ * @param keys the keys of the exchange
+ * @param exchange representation of the exchange
*/
static void
-process_pay_with_exchange (
+process_pay_with_keys (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *exchange_handle,
- const char *payto_uri,
- const struct TALER_Amount *wire_fee,
- bool exchange_trusted)
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
{
struct ExchangeGroup *eg = cls;
struct PayContext *pc = eg->pc;
struct TMH_HandlerContext *hc = pc->hc;
- const struct TALER_EXCHANGE_Keys *keys;
unsigned int group_size;
- (void) payto_uri;
eg->fo = NULL;
+ pc->pending_at_eg--;
+ GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Processing payment with exchange %s\n",
- (NULL == exchange_handle)
- ? "<null>"
- : TALER_EXCHANGE_get_base_url (exchange_handle));
+ eg->exchange_url);
GNUNET_assert (GNUNET_YES == pc->suspended);
- if (NULL == hr)
+ if (NULL == keys)
{
- pc->pending_at_eg--;
- resume_pay_with_response (
+ GNUNET_break_op (0);
+ resume_pay_with_error (
pc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT)));
+ TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
+ NULL);
return;
}
- if ( (MHD_HTTP_OK != hr->http_status) ||
- (NULL == exchange_handle) )
+
+ if (GNUNET_OK !=
+ TMH_exchange_check_debit (exchange,
+ pc->wm))
{
- pc->pending_at_eg--;
- resume_pay_with_response (
- pc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE),
- TMH_pack_exchange_reply (hr)));
+ if (eg->tried_force_keys)
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (
+ pc,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED,
+ NULL);
+ return;
+ }
+ force_keys (eg);
return;
}
- keys = TALER_EXCHANGE_get_keys (exchange_handle);
- if (NULL == keys)
+
+ if (GNUNET_OK !=
+ TMH_EXCHANGES_lookup_wire_fee (exchange,
+ pc->wm->wire_method,
+ &eg->wire_fee))
{
- pc->pending_at_eg--;
- GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK */
- resume_pay_with_error (pc,
- MHD_HTTP_BAD_GATEWAY,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE,
- NULL);
+ if (eg->tried_force_keys)
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (
+ pc,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED,
+ pc->wm->wire_method);
+ return;
+ }
+ force_keys (eg);
return;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got wire data for %s\n",
+ eg->exchange_url);
/* Initiate /batch-deposit operation for all coins of
the current exchange (!) */
group_size = 0;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
- unsigned int http_status;
- enum TALER_ErrorCode ec;
bool is_age_restricted_denom = false;
if (0 != strcmp (eg->exchange_url,
@@ -1247,74 +1001,45 @@ process_pay_with_exchange (
&dc->cdd.h_denom_pub);
if (NULL == denom_details)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Denomination not found, re-fetching /keys\n");
- if (! eg->tried_force_keys)
+ if (eg->tried_force_keys)
{
- /* let's try *forcing* a re-download of /keys from the exchange.
- Maybe the wallet has seen /keys that we missed. */
- eg->tried_force_keys = true;
- eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
- pc->wm->wire_method,
- GNUNET_YES,
- &process_pay_with_exchange,
- eg);
- if (NULL != eg->fo)
- return;
+ GNUNET_break_op (0);
+ resume_pay_with_response (
+ pc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &dc->cdd.h_denom_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal (
+ "exchange_keys",
+ TALER_EXCHANGE_keys_to_json (keys)))));
+ return;
}
- /* Forcing failed or we already did it, give up */
- pc->pending_at_eg--;
- resume_pay_with_response (
- pc,
- MHD_HTTP_BAD_REQUEST,
- TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &dc->cdd.h_denom_pub),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref (
- "exchange_keys",
- (json_t *) TALER_EXCHANGE_get_keys_raw (exchange_handle)))));
+ force_keys (eg);
return;
}
dc->deposit_fee = denom_details->fees.deposit;
dc->refund_fee = denom_details->fees.refund;
- if (GNUNET_OK !=
- TMH_AUDITORS_check_dk (exchange_handle,
- denom_details,
- exchange_trusted,
- &http_status,
- &ec))
+ if (GNUNET_TIME_absolute_is_past (
+ denom_details->expire_deposit.abs_time))
{
- if (! eg->tried_force_keys)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Denomination not audited by trusted auditor, re-fetching /keys\n");
- /* let's try *forcing* a re-download of /keys from the exchange.
- Maybe the wallet has seen auditors that we missed. */
- eg->tried_force_keys = true;
- eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
- pc->wm->wire_method,
- GNUNET_YES,
- &process_pay_with_exchange,
- eg);
- if (NULL != eg->fo)
- return;
- }
- pc->pending_at_eg--;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Denomination key offered by client has expired for deposits\n");
resume_pay_with_response (
pc,
- http_status,
+ MHD_HTTP_GONE,
TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_ec (ec),
+ TALER_JSON_pack_ec (
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED),
GNUNET_JSON_pack_data_auto ("h_denom_pub",
&denom_details->h_key)));
return;
}
-
/* Now that we have the details about the denomination, we can verify age
* restriction requirements, if applicable. Note that denominations with an
* age_mask equal to zero always pass the age verification. */
@@ -1351,7 +1076,7 @@ process_pay_with_exchange (
AGE_FAIL:
if (0 < code)
{
- pc->pending_at_eg--;
+ GNUNET_break_op (0);
GNUNET_free (dc->age_commitment.keys);
resume_pay_with_response (
pc,
@@ -1369,13 +1094,13 @@ AGE_FAIL:
&dc->cdd.h_age_commitment);
GNUNET_free (dc->age_commitment.keys);
}
- else if (is_age_restricted_denom && dc->no_h_age_commitment)
+ else if (is_age_restricted_denom &&
+ dc->no_h_age_commitment)
{
/* The contract did not ask for a minimum_age but the client paid
* with a coin that has age restriction enabled. We lack the hash
* of the age commitment in this case in order to verify the coin
* and to deposit it with the exchange. */
- pc->pending_at_eg--;
GNUNET_break_op (0);
resume_pay_with_response (
pc,
@@ -1393,12 +1118,15 @@ AGE_FAIL:
if (0 == group_size)
{
GNUNET_break (0);
- pc->pending_at_eg--;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Group size zero, %u batch transactions remain pending\n",
pc->pending_at_eg);
if (0 == pc->pending_at_eg)
- execute_pay_transaction (pc);
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
+ return;
+ }
return;
}
@@ -1409,14 +1137,14 @@ AGE_FAIL:
.merchant_payto_uri = pc->wm->payto_uri,
.wire_salt = pc->wm->wire_salt,
.h_contract_terms = pc->h_contract_terms,
- .policy_details = NULL, /* FIXME-oec #7270 */
- .timestamp = pc->timestamp,
+ .wallet_timestamp = pc->timestamp,
.merchant_pub = hc->instance->merchant_pub,
.refund_deadline = pc->refund_deadline
};
enum TALER_ErrorCode ec;
+ size_t off = 0;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1425,25 +1153,27 @@ AGE_FAIL:
if (0 != strcmp (dc->exchange_url,
eg->exchange_url))
continue;
- cdds[i] = dc->cdd;
- dc->wire_fee = *wire_fee;
+ GNUNET_assert (off < group_size);
+ cdds[off++] = dc->cdd;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Initiating batch deposit with %u coins\n",
group_size);
- eg->bdh = TALER_EXCHANGE_batch_deposit (exchange_handle,
- &dcd,
- group_size,
- cdds,
- &batch_deposit_cb,
- eg,
- &ec);
+ eg->bdh = TALER_EXCHANGE_batch_deposit (
+ TMH_curl_ctx,
+ eg->exchange_url,
+ keys,
+ &dcd,
+ group_size,
+ cdds,
+ &batch_deposit_cb,
+ eg,
+ &ec);
if (NULL == eg->bdh)
{
/* Signature was invalid or some other constraint was not satisfied. If
the exchange was unavailable, we'd get that information in the
callback. */
- pc->pending_at_eg--;
GNUNET_break_op (0);
resume_pay_with_response (
pc,
@@ -1454,27 +1184,96 @@ AGE_FAIL:
eg->exchange_url)));
return;
}
+ pc->pending_at_eg++;
if (TMH_force_audit)
TALER_EXCHANGE_batch_deposit_force_dc (eg->bdh);
}
}
+static void
+force_keys (struct ExchangeGroup *eg)
+{
+ struct PayContext *pc = eg->pc;
+
+ eg->tried_force_keys = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Forcing /keys download (once) as wire fees are unknown\n");
+ eg->fo = TMH_EXCHANGES_keys4exchange (
+ eg->exchange_url,
+ true,
+ &process_pay_with_keys,
+ eg);
+ if (NULL == eg->fo)
+ {
+ GNUNET_break (0);
+ resume_pay_with_error (pc,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
+ "Failed to lookup exchange by URL");
+ return;
+ }
+ pc->pending_at_eg++;
+}
+
+
+/**
+ * Handle a timeout for the processing of the pay request.
+ *
+ * @param cls our `struct PayContext`
+ */
+static void
+handle_pay_timeout (void *cls)
+{
+ struct PayContext *pc = cls;
+
+ pc->timeout_task = NULL;
+ GNUNET_assert (GNUNET_YES == pc->suspended);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming pay with error after timeout\n");
+ resume_pay_with_error (pc,
+ TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
+ NULL);
+}
+
+
+/**
+ * Compute the timeout for a /pay request based on the number of coins
+ * involved.
+ *
+ * @param num_coins number of coins
+ * @returns timeout for the /pay request
+ */
+static struct GNUNET_TIME_Relative
+get_pay_timeout (unsigned int num_coins)
+{
+ struct GNUNET_TIME_Relative t;
+
+ /* FIXME-Performance-Optimization: Do some benchmarking to come up with a
+ * better timeout. We've increased this value so the wallet integration
+ * test passes again on my (Florian) machine.
+ */
+ t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+ 15 * (1 + (num_coins / 5)));
+
+ return t;
+}
+
+
/**
* Start batch deposits for all exchanges involved
* in this payment.
*
- * @param pc payment context we are processing
+ * @param[in,out] pc payment context we are processing
*/
static void
-start_batch_deposits (struct PayContext *pc)
+phase_batch_deposits (struct PayContext *pc)
{
for (unsigned int i = 0; i<pc->num_exchanges; i++)
{
struct ExchangeGroup *eg = pc->egs[i];
bool have_coins = false;
- for (unsigned int j = 0; j<pc->coins_cnt; j++)
+ for (size_t j = 0; j<pc->coins_cnt; j++)
{
struct DepositConfirmation *dc = &pc->dc[j];
@@ -1488,24 +1287,131 @@ start_batch_deposits (struct PayContext *pc)
}
if (! have_coins)
continue; /* no coins left to deposit at this exchange */
- eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
- pc->wm->wire_method,
- GNUNET_NO,
- &process_pay_with_exchange,
- eg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Getting /keys for %s\n",
+ eg->exchange_url);
+ eg->fo = TMH_EXCHANGES_keys4exchange (
+ eg->exchange_url,
+ false,
+ &process_pay_with_keys,
+ eg);
if (NULL == eg->fo)
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
- "Failed to lookup exchange by URL");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
+ "Failed to lookup exchange by URL"));
return;
}
pc->pending_at_eg++;
}
if (0 == pc->pending_at_eg)
- execute_pay_transaction (pc);
+ {
+ pc->phase = PP_PAY_TRANSACTION;
+ pay_resume (pc);
+ return;
+ }
+ /* Suspend while we interact with the exchange */
+ MHD_suspend_connection (pc->connection);
+ pc->suspended = GNUNET_YES;
+ GNUNET_assert (NULL == pc->timeout_task);
+ pc->timeout_task
+ = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt),
+ &handle_pay_timeout,
+ pc);
+}
+
+
+/**
+ * Generate response (payment successful)
+ *
+ * @param[in,out] pc payment context where the payment was successful
+ */
+static void
+phase_success_response (struct PayContext *pc)
+{
+ struct TALER_MerchantSignatureP sig;
+ char *pos_confirmation;
+
+ /* Sign on our end (as the payment did go through, even if it may
+ have been refunded already) */
+ TALER_merchant_pay_sign (&pc->h_contract_terms,
+ &pc->hc->instance->merchant_priv,
+ &sig);
+ /* Build the response */
+ pos_confirmation = (NULL == pc->pos_key)
+ ? NULL
+ : TALER_build_pos_confirmation (pc->pos_key,
+ pc->pos_alg,
+ &pc->amount,
+ pc->timestamp);
+ pay_end (pc,
+ TALER_MHD_REPLY_JSON_PACK (
+ pc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("pos_confirmation",
+ pos_confirmation)),
+ GNUNET_JSON_pack_data_auto ("sig",
+ &sig)));
+ GNUNET_free (pos_confirmation);
+}
+
+
+/**
+ * Use database to notify other clients about the
+ * payment being completed.
+ *
+ * @param[in,out] pc context to trigger notification for
+ */
+static void
+phase_payment_notification (struct PayContext *pc)
+{
+ {
+ struct TMH_OrderPayEventP pay_eh = {
+ .header.size = htons (sizeof (pay_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
+ .merchant_pub = pc->hc->instance->merchant_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying clients about payment of order %s\n",
+ pc->order_id);
+ GNUNET_CRYPTO_hash (pc->order_id,
+ strlen (pc->order_id),
+ &pay_eh.h_order_id);
+ TMH_db->event_notify (TMH_db->cls,
+ &pay_eh.header,
+ NULL,
+ 0);
+ }
+ if ( (NULL != pc->session_id) &&
+ (NULL != pc->fulfillment_url) )
+ {
+ struct TMH_SessionEventP session_eh = {
+ .header.size = htons (sizeof (session_eh)),
+ .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
+ .merchant_pub = pc->hc->instance->merchant_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying clients about session change to %s for %s\n",
+ pc->session_id,
+ pc->fulfillment_url);
+ GNUNET_CRYPTO_hash (pc->session_id,
+ strlen (pc->session_id),
+ &session_eh.h_session_id);
+ GNUNET_CRYPTO_hash (pc->fulfillment_url,
+ strlen (pc->fulfillment_url),
+ &session_eh.h_fulfillment_url);
+ TMH_db->event_notify (TMH_db->cls,
+ &session_eh.header,
+ NULL,
+ 0);
+ }
+ pc->phase = PP_SUCCESS_RESPONSE;
}
@@ -1518,7 +1424,6 @@ start_batch_deposits (struct PayContext *pc)
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
- * @param wire_fee wire fee the exchange of this coin charges
*/
static void
check_coin_paid (void *cls,
@@ -1526,12 +1431,11 @@ check_coin_paid (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee,
- const struct TALER_Amount *wire_fee)
+ const struct TALER_Amount *refund_fee)
{
struct PayContext *pc = cls;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1543,13 +1447,26 @@ check_coin_paid (void *cls,
(0 !=
strcmp (exchange_url,
dc->exchange_url)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (amount_with_fee,
+ &dc->cdd.amount)) ||
(0 != TALER_amount_cmp (amount_with_fee,
&dc->cdd.amount)) )
continue; /* does not match, skip */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Deposit of coin `%s' already in our DB.\n",
TALER_B2S (coin_pub));
-
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->total_paid,
+ amount_with_fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->total_fees_paid,
+ deposit_fee)) )
+ {
+ GNUNET_break_op (0);
+ pc->deposit_currency_mismatch = true;
+ break;
+ }
GNUNET_assert (0 <=
TALER_amount_add (&pc->total_paid,
&pc->total_paid,
@@ -1560,7 +1477,6 @@ check_coin_paid (void *cls,
deposit_fee));
dc->deposit_fee = *deposit_fee;
dc->refund_fee = *refund_fee;
- dc->wire_fee = *wire_fee;
dc->cdd.amount = *amount_with_fee;
dc->found_in_db = true;
pc->pending--;
@@ -1593,7 +1509,7 @@ check_coin_refunded (void *cls,
an abort-pay refund (an unusual but possible case), we need
to make sure that existing refunds are accounted for. */
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
@@ -1601,6 +1517,14 @@ check_coin_refunded (void *cls,
if (0 != GNUNET_memcmp (coin_pub,
&dc->cdd.coin_pub))
continue;
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->total_refunded,
+ refund_amount))
+ {
+ GNUNET_break (0);
+ pc->refund_currency_mismatch = true;
+ break;
+ }
GNUNET_assert (0 <=
TALER_amount_add (&pc->total_refunded,
&pc->total_refunded,
@@ -1623,30 +1547,75 @@ check_payment_sufficient (struct PayContext *pc)
struct TALER_Amount acc_fee;
struct TALER_Amount acc_amount;
struct TALER_Amount final_amount;
- struct TALER_Amount wire_fee_delta;
- struct TALER_Amount wire_fee_customer_contribution;
struct TALER_Amount total_wire_fee;
struct TALER_Amount total_needed;
if (0 == pc->coins_cnt)
+ return TALER_amount_is_zero (&pc->amount);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &total_wire_fee));
+ for (unsigned int i = 0; i < pc->num_exchanges; i++)
{
- return ((0 == pc->amount.value) &&
- (0 == pc->amount.fraction));
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&total_wire_fee,
+ &pc->egs[i]->wire_fee))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ total_wire_fee.currency));
+ return false;
+ }
+ if (0 >
+ TALER_amount_add (&total_wire_fee,
+ &total_wire_fee,
+ &pc->egs[i]->wire_fee))
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
+ "could not add exchange wire fee to total"));
+ return false;
+ }
}
- acc_fee = pc->dc[0].deposit_fee;
- total_wire_fee = pc->dc[0].wire_fee;
- acc_amount = pc->dc[0].cdd.amount;
-
/**
* This loops calculates what are the deposit fee / total
* amount with fee / and wire fee, for all the coins.
*/
- for (unsigned int i = 1; i<pc->coins_cnt; i++)
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &acc_fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pc->amount.currency,
+ &acc_amount));
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dc = &pc->dc[i];
GNUNET_assert (dc->found_in_db);
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&acc_fee,
+ &dc->deposit_fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&acc_amount,
+ &dc->cdd.amount)) )
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ dc->deposit_fee.currency));
+ return false;
+ }
if ( (0 >
TALER_amount_add (&acc_fee,
&dc->deposit_fee,
@@ -1658,10 +1627,12 @@ check_payment_sufficient (struct PayContext *pc)
{
GNUNET_break (0);
/* Overflow in these amounts? Very strange. */
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts"));
return false;
}
if (1 ==
@@ -1669,53 +1640,15 @@ check_payment_sufficient (struct PayContext *pc)
&dc->cdd.amount))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT,
- "Deposit fees exceed coin's contribution");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT,
+ "Deposit fees exceed coin's contribution"));
return false;
}
-
- /* If exchange differs, add wire fee */
- {
- bool new_exchange = true;
-
- for (unsigned int j = 0; j<i; j++)
- if (0 == strcasecmp (dc->exchange_url,
- pc->dc[j].exchange_url))
- {
- new_exchange = false;
- break;
- }
-
- if (! new_exchange)
- continue;
-
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (&total_wire_fee,
- &dc->wire_fee))
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- total_wire_fee.currency);
- return false;
- }
- if (0 >
- TALER_amount_add (&total_wire_fee,
- &total_wire_fee,
- &dc->wire_fee))
- {
- GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
- "could not add exchange wire fee to total");
- return false;
- }
- }
- } /* deposit loop */
+ } /* end deposit loop */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Amount received from wallet: %s\n",
@@ -1727,9 +1660,6 @@ check_payment_sufficient (struct PayContext *pc)
"Total wire fee: %s\n",
TALER_amount2s (&total_wire_fee));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Max wire fee: %s\n",
- TALER_amount2s (&pc->max_wire_fee));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Deposit fee limit for merchant: %s\n",
TALER_amount2s (&pc->max_fee));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1737,53 +1667,34 @@ check_payment_sufficient (struct PayContext *pc)
TALER_amount2s (&pc->total_refunded));
/* Now compare exchange wire fee compared to
- * what we are willing to pay */
+ * what we are willing to pay */
if (GNUNET_YES !=
TALER_amount_cmp_currency (&total_wire_fee,
- &pc->max_wire_fee))
+ &acc_fee))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- total_wire_fee.currency);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ total_wire_fee.currency));
return false;
}
- switch (TALER_amount_subtract (&wire_fee_delta,
- &total_wire_fee,
- &pc->max_wire_fee))
- {
- case TALER_AAR_RESULT_POSITIVE:
- /* Actual wire fee is indeed higher than our maximum,
- compute how much the customer is expected to cover! */
- TALER_amount_divide (&wire_fee_customer_contribution,
- &wire_fee_delta,
- pc->wire_fee_amortization);
- break;
- case TALER_AAR_RESULT_ZERO:
- case TALER_AAR_INVALID_NEGATIVE_RESULT:
- /* Wire fee threshold is still above the wire fee amount.
- Customer is not going to contribute on this. */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (total_wire_fee.currency,
- &wire_fee_customer_contribution));
- break;
- default:
- GNUNET_assert (0);
- }
-
- /* add wire fee contribution to the total fees */
+ /* add wire fee to the total fees */
if (0 >
TALER_amount_add (&acc_fee,
&acc_fee,
- &wire_fee_customer_contribution))
+ &total_wire_fee))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts"));
return false;
}
if (-1 == TALER_amount_cmp (&pc->max_fee,
@@ -1808,10 +1719,12 @@ check_payment_sufficient (struct PayContext *pc)
&pc->amount))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
- "Overflow adding up amounts");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+ "Overflow adding up amounts"));
return false;
}
}
@@ -1832,10 +1745,12 @@ check_payment_sufficient (struct PayContext *pc)
&pc->total_refunded))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS,
- "refunded amount exceeds total payments");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS,
+ "refunded amount exceeds total payments"));
return false;
}
@@ -1847,28 +1762,33 @@ check_payment_sufficient (struct PayContext *pc)
&total_needed))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_PAYMENT_REQUIRED,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED,
- "contract not paid up due to refunds");
- }
- else if (-1 < TALER_amount_cmp (&acc_amount,
- &pc->amount))
- {
- GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES,
- "contract not paid up due to fees (client may have calculated them badly)");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED,
+ "contract not paid up due to refunds"));
+ return false;
}
- else
+ if (-1 < TALER_amount_cmp (&acc_amount,
+ &pc->amount))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
- "payment insufficient");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES,
+ "contract not paid up due to fees (client may have calculated them badly)"));
+ return false;
}
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
+ "payment insufficient"));
return false;
}
return true;
@@ -1876,86 +1796,14 @@ check_payment_sufficient (struct PayContext *pc)
/**
- * Use database to notify other clients about the
- * payment being completed.
- *
- * @param pc context to trigger notification for
- */
-static void
-trigger_payment_notification (struct PayContext *pc)
-{
- {
- struct TMH_OrderPayEventP pay_eh = {
- .header.size = htons (sizeof (pay_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
- .merchant_pub = pc->hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying clients about payment of order %s\n",
- pc->order_id);
- GNUNET_CRYPTO_hash (pc->order_id,
- strlen (pc->order_id),
- &pay_eh.h_order_id);
- TMH_db->event_notify (TMH_db->cls,
- &pay_eh.header,
- NULL,
- 0);
- }
- if ( (NULL != pc->session_id) &&
- (NULL != pc->fulfillment_url) )
- {
- struct TMH_SessionEventP session_eh = {
- .header.size = htons (sizeof (session_eh)),
- .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
- .merchant_pub = pc->hc->instance->merchant_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying clients about session change to %s for %s\n",
- pc->session_id,
- pc->fulfillment_url);
- GNUNET_CRYPTO_hash (pc->session_id,
- strlen (pc->session_id),
- &session_eh.h_session_id);
- GNUNET_CRYPTO_hash (pc->fulfillment_url,
- strlen (pc->fulfillment_url),
- &session_eh.h_fulfillment_url);
- TMH_db->event_notify (TMH_db->cls,
- &session_eh.header,
- NULL,
- 0);
- }
-}
-
-
-/**
- * Generate response (payment successful)
+ * Execute the DB transaction. If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
*
- * @param[in,out] pc payment context where the payment was successful
+ * @param[in,out] pc payment context to transact
*/
static void
-generate_success_response (struct PayContext *pc)
-{
- struct GNUNET_CRYPTO_EddsaSignature sig;
-
- /* Sign on our end (as the payment did go through, even if it may
- have been refunded already) */
- TALER_merchant_pay_sign (&pc->h_contract_terms,
- &pc->hc->instance->merchant_priv,
- &sig);
- /* Build the response */
- resume_pay_with_response (
- pc,
- MHD_HTTP_OK,
- TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("sig",
- &sig)));
-}
-
-
-static void
-execute_pay_transaction (struct PayContext *pc)
+phase_execute_pay_transaction (struct PayContext *pc)
{
struct TMH_HandlerContext *hc = pc->hc;
const char *instance_id = hc->instance->settings.id;
@@ -1964,13 +1812,13 @@ execute_pay_transaction (struct PayContext *pc)
if (pc->retry_counter++ > MAX_RETRIES)
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL));
return;
}
- GNUNET_assert (GNUNET_YES == pc->suspended);
/* Initialize some amount accumulators
(used in check_coin_paid(), check_coin_refunded()
@@ -1984,7 +1832,7 @@ execute_pay_transaction (struct PayContext *pc)
GNUNET_break (GNUNET_OK ==
TALER_amount_set_zero (pc->amount.currency,
&pc->total_refunded));
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
pc->dc[i].found_in_db = false;
pc->pending = pc->coins_cnt;
@@ -1995,10 +1843,11 @@ execute_pay_transaction (struct PayContext *pc)
"run pay"))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL));
return;
}
@@ -2015,21 +1864,28 @@ execute_pay_transaction (struct PayContext *pc)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
/* 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_GENERIC_DB_FETCH_FAILED,
- "lookup deposits");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup deposits"));
+ return;
+ }
+ if (pc->deposit_currency_mismatch)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ pc->amount.currency));
return;
}
}
-
{
enum GNUNET_DB_QueryStatus qs;
@@ -2043,16 +1899,24 @@ execute_pay_transaction (struct PayContext *pc)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
/* 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_GENERIC_DB_FETCH_FAILED,
- "lookup refunds");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup refunds"));
+ return;
+ }
+ if (pc->refund_currency_mismatch)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "refund currency in database does not match order currency"));
return;
}
}
@@ -2062,11 +1926,10 @@ execute_pay_transaction (struct PayContext *pc)
{
/* we made no DB changes, so we can just rollback */
TMH_db->rollback (TMH_db->cls);
-
/* Ok, we need to first go to the network to process more coins.
We that interaction in *tiny* transactions (hence the rollback
above). */
- start_batch_deposits (pc);
+ pc->phase = PP_BATCH_DEPOSITS;
return;
}
@@ -2094,15 +1957,13 @@ execute_pay_transaction (struct PayContext *pc)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "mark contract paid");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "mark contract paid"));
return;
}
}
@@ -2113,23 +1974,30 @@ execute_pay_transaction (struct PayContext *pc)
pc->order_serial);
{
enum GNUNET_DB_QueryStatus qs;
-
+ json_t *jhook;
+
+ jhook = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("contract_terms",
+ pc->contract_terms),
+ GNUNET_JSON_pack_string ("order_id",
+ pc->order_id)
+ );
+ GNUNET_assert (NULL != jhook);
qs = TMH_trigger_webhook (pc->hc->instance->settings.id,
"pay",
- pc->contract_terms);
+ jhook);
+ json_decref (jhook);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "failed to trigger webhooks");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "failed to trigger webhooks"));
return;
}
}
@@ -2143,237 +2011,17 @@ execute_pay_transaction (struct PayContext *pc)
/* commit failed */
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- execute_pay_transaction (pc);
- return;
- }
+ return; /* do it again */
GNUNET_break (0);
- resume_pay_with_error (pc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL));
return;
}
- trigger_payment_notification (pc);
}
- generate_success_response (pc);
-}
-
-
-/**
- * Try to parse the pay request into the given pay context.
- * Schedules an error response in the connection on failure.
- *
- * @param[in,out] pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- * #GNUNET_NO on failure (response was queued with MHD)
- * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
- */
-static enum GNUNET_GenericReturnValue
-parse_pay (struct PayContext *pc)
-{
- const char *session_id = NULL;
- json_t *coins;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("coins",
- &coins),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("session_id",
- &session_id),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (pc->connection,
- pc->hc->request_body,
- spec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- return res;
- }
- }
-
- /* copy session ID (if set) */
- if (NULL != session_id)
- {
- pc->session_id = GNUNET_strdup (session_id);
- }
- else
- {
- /* use empty string as default if client didn't specify it */
- pc->session_id = GNUNET_strdup ("");
- }
-
- if (! json_is_array (coins))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- "'coins' must be an array"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- pc->coins_cnt = json_array_size (coins);
- if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "'coins' array too long"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- /* 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' */
- {
- unsigned int coins_index;
- json_t *coin;
-
- json_array_foreach (coins, coins_index, coin)
- {
- struct DepositConfirmation *dc = &pc->dc[coins_index];
- const char *exchange_url;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &dc->cdd.coin_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &dc->cdd.coin_pub),
- TALER_JSON_spec_denom_sig ("ub_sig",
- &dc->cdd.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("h_denom",
- &dc->cdd.h_denom_pub),
- TALER_JSON_spec_amount ("contribution",
- TMH_currency,
- &dc->cdd.amount),
- GNUNET_JSON_spec_string ("exchange_url",
- &exchange_url),
- /* if a minimum age was required, the minimum_age_sig and
- * age_commitment must be provided */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
- &dc->minimum_age_sig),
- &dc->no_minimum_age_sig),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_age_commitment ("age_commitment",
- &dc->age_commitment),
- &dc->no_age_commitment),
- /* if minimum age was not required, but coin with age restriction set
- * was used, h_age_commitment must be provided. */
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &dc->cdd.h_age_commitment),
- &dc->no_h_age_commitment),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
- bool have_eg = false;
-
- res = TALER_MHD_parse_json_data (pc->connection,
- coin,
- ispec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-
- for (unsigned int j = 0; j<coins_index; j++)
- {
- if (0 ==
- GNUNET_memcmp (&dc->cdd.coin_pub,
- &pc->dc[j].cdd.coin_pub))
- {
- GNUNET_break_op (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "duplicate coin in list"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- }
-
- dc->exchange_url = GNUNET_strdup (exchange_url);
- dc->index = coins_index;
- dc->pc = pc;
-
- if (0 !=
- strcasecmp (dc->cdd.amount.currency,
- TMH_currency))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- TMH_currency))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- /* Check the consistency of the (potential) age restriction
- * information. */
- if (dc->no_age_commitment != dc->no_minimum_age_sig)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inconsistent: 'age_commitment' vs. 'minimum_age_sig'"
- )
- )
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- /* Setup exchange group */
- for (unsigned int i = 0; i<pc->num_exchanges; i++)
- {
- if (0 ==
- strcmp (pc->egs[i]->exchange_url,
- GNUNET_strdup (exchange_url)))
- {
- have_eg = true;
- break;
- }
- }
- if (! have_eg)
- {
- struct ExchangeGroup *eg;
-
- eg = GNUNET_new (struct ExchangeGroup);
- eg->pc = pc;
- eg->exchange_url = dc->exchange_url;
- GNUNET_array_append (pc->egs,
- pc->num_exchanges,
- eg);
- }
- }
- }
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
+ pc->phase = PP_PAYMENT_NOTIFICATION;
}
@@ -2384,9 +2032,10 @@ parse_pay (struct PayContext *pc)
* @param cls closure with our `struct PayContext *`
* @param deposit_serial which deposit operation is this about
* @param exchange_url URL of the exchange that issued the coin
+ * @param h_wire hash of merchant's wire details
+ * @param deposit_timestamp when was the deposit made
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
- * @param h_wire hash of merchant's wire details
* @param coin_pub public key of the coin
*/
static void
@@ -2395,13 +2044,14 @@ deposit_paid_check (
uint64_t deposit_serial,
const char *exchange_url,
const struct TALER_MerchantWireHashP *h_wire,
+ struct GNUNET_TIME_Timestamp deposit_timestamp,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct PayContext *pc = cls;
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
@@ -2411,6 +2061,9 @@ deposit_paid_check (
(0 ==
strcmp (dci->exchange_url,
exchange_url)) &&
+ (GNUNET_YES ==
+ TALER_amount_cmp_currency (&dci->cdd.amount,
+ amount_with_fee)) &&
(0 ==
TALER_amount_cmp (&dci->cdd.amount,
amount_with_fee)) )
@@ -2427,11 +2080,9 @@ deposit_paid_check (
* the payment is idempotent, or refunds the excess payment.
*
* @param[in,out] pc context we use to handle the payment
- * @return #GNUNET_NO if response was queued with MHD
- * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
*/
-static enum GNUNET_GenericReturnValue
-handle_contract_paid (struct PayContext *pc)
+static void
+phase_contract_paid (struct PayContext *pc)
{
enum GNUNET_DB_QueryStatus qs;
bool unmatched = false;
@@ -2444,15 +2095,14 @@ handle_contract_paid (struct PayContext *pc)
if (qs <= 0)
{
GNUNET_break (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_deposits_by_order"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_deposits_by_order"));
+ return;
}
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
@@ -2462,7 +2112,7 @@ handle_contract_paid (struct PayContext *pc)
if (! unmatched)
{
/* Everything fine, idempotent request */
- struct GNUNET_CRYPTO_EddsaSignature sig;
+ struct TALER_MerchantSignatureP sig;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Idempotent pay request for order `%s', signing again\n",
@@ -2470,14 +2120,13 @@ handle_contract_paid (struct PayContext *pc)
TALER_merchant_pay_sign (&pc->h_contract_terms,
&pc->hc->instance->merchant_priv,
&sig);
- return (MHD_YES ==
- TALER_MHD_REPLY_JSON_PACK (
- pc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("sig",
- &sig)))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_REPLY_JSON_PACK (
+ pc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("sig",
+ &sig)));
+ return;
}
/* Conflict, double-payment detected! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -2485,7 +2134,7 @@ handle_contract_paid (struct PayContext *pc)
pc->order_id);
refunds = json_array ();
GNUNET_assert (NULL != refunds);
- for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
struct TALER_MerchantSignatureP merchant_sig;
@@ -2514,29 +2163,25 @@ handle_contract_paid (struct PayContext *pc)
GNUNET_JSON_pack_uint64 ("rtransaction_id",
0))));
}
- return (MHD_YES ==
- TALER_MHD_REPLY_JSON_PACK (
- pc->connection,
- MHD_HTTP_CONFLICT,
- TALER_MHD_PACK_EC (
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID),
- GNUNET_JSON_pack_array_steal ("refunds",
- refunds)))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_REPLY_JSON_PACK (
+ pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_MHD_PACK_EC (
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID),
+ GNUNET_JSON_pack_array_steal ("refunds",
+ refunds)));
}
/**
- * Check the database state for the given order. * Schedules an error response in the connection on failure.
+ * Check the database state for the given order.
+ * Schedules an error response in the connection on failure.
*
- * @param pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- * #GNUNET_NO on failure (response was queued with MHD)
- * #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ * @param[in,out] pc context we use to handle the payment
*/
-static enum GNUNET_GenericReturnValue
-check_contract (struct PayContext *pc)
+static void
+phase_check_contract (struct PayContext *pc)
{
/* obtain contract terms */
enum GNUNET_DB_QueryStatus qs;
@@ -2547,13 +2192,15 @@ check_contract (struct PayContext *pc)
json_decref (pc->contract_terms);
pc->contract_terms = NULL;
}
- qs = TMH_db->lookup_contract_terms (TMH_db->cls,
- pc->hc->instance->settings.id,
- pc->order_id,
- &pc->contract_terms,
- &pc->order_serial,
- &paid,
- NULL);
+ qs = TMH_db->lookup_contract_terms2 (TMH_db->cls,
+ pc->hc->instance->settings.id,
+ pc->order_id,
+ &pc->contract_terms,
+ &pc->order_serial,
+ &paid,
+ NULL,
+ &pc->pos_key,
+ &pc->pos_alg);
if (0 > qs)
{
/* single, read-only SQL statements should never cause
@@ -2561,44 +2208,45 @@ check_contract (struct PayContext *pc)
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
/* Always report on hard error to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "contract terms"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "contract terms"));
+ return;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
- pc->order_id))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+ pc->order_id));
+ return;
}
/* hash contract (needed later) */
+ json_dumpf (pc->contract_terms,
+ stderr,
+ JSON_INDENT (2));
if (GNUNET_OK !=
TALER_JSON_contract_hash (pc->contract_terms,
&pc->h_contract_terms))
{
GNUNET_break (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ NULL));
+ return;
}
if (paid)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order `%s' paid, checking for double-payment\n",
pc->order_id);
- return handle_contract_paid (pc);
+ pc->phase = PP_CONTRACT_PAID;
+ return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling payment for order `%s' with contract hash `%s'\n",
@@ -2611,34 +2259,27 @@ check_contract (struct PayContext *pc)
{
/* invalid contract */
GNUNET_break (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
+ NULL));
+ return;
}
/* Get details from contract and check fundamentals */
{
const char *fulfillment_url = NULL;
struct GNUNET_JSON_Specification espec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &pc->amount),
+ TALER_JSON_spec_amount_any ("amount",
+ &pc->amount),
GNUNET_JSON_spec_mark_optional (
+ /* This one does not have to be a Web URL */
GNUNET_JSON_spec_string ("fulfillment_url",
&fulfillment_url),
NULL),
- TALER_JSON_spec_amount ("max_fee",
- TMH_currency,
- &pc->max_fee),
- TALER_JSON_spec_amount ("max_wire_fee",
- TMH_currency,
- &pc->max_wire_fee),
- GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
- &pc->wire_fee_amortization),
+ TALER_JSON_spec_amount_any ("max_fee",
+ &pc->max_fee),
GNUNET_JSON_spec_timestamp ("timestamp",
&pc->timestamp),
GNUNET_JSON_spec_timestamp ("refund_deadline",
@@ -2666,7 +2307,42 @@ check_contract (struct PayContext *pc)
if (GNUNET_YES != res)
{
GNUNET_break (0);
- return res;
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+ }
+
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->max_fee,
+ &pc->amount))
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "'max_fee' in database does not match currency of contract price"));
+ return;
+ }
+
+ for (size_t i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&dc->cdd.amount,
+ &pc->amount))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ pc->amount.currency));
+ return;
}
}
@@ -2676,21 +2352,22 @@ check_contract (struct PayContext *pc)
{
/* This should already have been checked when creating the order! */
GNUNET_break (0);
- return TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
+ NULL));
+ return;
}
if (GNUNET_TIME_absolute_is_past (pc->pay_deadline.abs_time))
{
/* too late */
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_GONE,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_GONE,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
+ NULL));
+ return;
}
/* Make sure wire method (still) exists for this instance */
@@ -2704,36 +2381,252 @@ check_contract (struct PayContext *pc)
if (NULL == wm)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
- NULL);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
+ NULL));
+ return;
}
pc->wm = wm;
}
+ pc->phase = PP_PAY_TRANSACTION;
+}
+
+
+/**
+ * Try to parse the pay request into the given pay context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_parse_pay (struct PayContext *pc)
+{
+ const char *session_id = NULL;
+ const json_t *coins;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("coins",
+ &coins),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("session_id",
+ &session_id),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (PP_INIT == pc->phase);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ pc->hc->request_body,
+ spec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+ }
+
+ /* copy session ID (if set) */
+ if (NULL != session_id)
+ {
+ pc->session_id = GNUNET_strdup (session_id);
+ }
+ else
+ {
+ /* use empty string as default if client didn't specify it */
+ pc->session_id = GNUNET_strdup ("");
+ }
+ pc->coins_cnt = json_array_size (coins);
+ if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'coins' array too long"));
+ return;
+ }
+
+ /* 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' */
+ {
+ unsigned int coins_index;
+ json_t *coin;
+
+ json_array_foreach (coins, coins_index, coin)
+ {
+ struct DepositConfirmation *dc = &pc->dc[coins_index];
+ const char *exchange_url;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &dc->cdd.coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &dc->cdd.coin_pub),
+ TALER_JSON_spec_denom_sig ("ub_sig",
+ &dc->cdd.denom_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_denom",
+ &dc->cdd.h_denom_pub),
+ TALER_JSON_spec_amount_any ("contribution",
+ &dc->cdd.amount),
+ TALER_JSON_spec_web_url ("exchange_url",
+ &exchange_url),
+ /* if a minimum age was required, the minimum_age_sig and
+ * age_commitment must be provided */
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
+ &dc->minimum_age_sig),
+ &dc->no_minimum_age_sig),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_age_commitment ("age_commitment",
+ &dc->age_commitment),
+ &dc->no_age_commitment),
+ /* if minimum age was not required, but coin with age restriction set
+ * was used, h_age_commitment must be provided. */
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &dc->cdd.h_age_commitment),
+ &dc->no_h_age_commitment),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ bool have_eg = false;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ coin,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+ for (unsigned int j = 0; j<coins_index; j++)
+ {
+ if (0 ==
+ GNUNET_memcmp (&dc->cdd.coin_pub,
+ &pc->dc[j].cdd.coin_pub))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate coin in list"));
+ return;
+ }
+ }
+
+ dc->exchange_url = GNUNET_strdup (exchange_url);
+ dc->index = coins_index;
+ dc->pc = pc;
- return GNUNET_OK;
+ /* Check the consistency of the (potential) age restriction
+ * information. */
+ if (dc->no_age_commitment != dc->no_minimum_age_sig)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inconsistent: 'age_commitment' vs. 'minimum_age_sig'"
+ ));
+ return;
+ }
+
+ /* Setup exchange group */
+ for (unsigned int i = 0; i<pc->num_exchanges; i++)
+ {
+ if (0 ==
+ strcmp (pc->egs[i]->exchange_url,
+ exchange_url))
+ {
+ have_eg = true;
+ break;
+ }
+ }
+ if (! have_eg)
+ {
+ struct ExchangeGroup *eg;
+
+ eg = GNUNET_new (struct ExchangeGroup);
+ eg->pc = pc;
+ eg->exchange_url = dc->exchange_url;
+ GNUNET_array_append (pc->egs,
+ pc->num_exchanges,
+ eg);
+ }
+ }
+ }
+ pc->phase = PP_CHECK_CONTRACT;
}
/**
- * Handle a timeout for the processing of the pay request.
+ * Custom cleanup routine for a `struct PayContext`.
*
- * @param cls our `struct PayContext`
+ * @param cls the `struct PayContext` to clean up.
*/
static void
-handle_pay_timeout (void *cls)
+pay_context_cleanup (void *cls)
{
struct PayContext *pc = cls;
- pc->timeout_task = NULL;
- GNUNET_assert (GNUNET_YES == pc->suspended);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming pay with error after timeout\n");
- resume_pay_with_error (pc,
- MHD_HTTP_GATEWAY_TIMEOUT,
- TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
- NULL);
+ if (NULL != pc->timeout_task)
+ {
+ GNUNET_SCHEDULER_cancel (pc->timeout_task);
+ pc->timeout_task = NULL;
+ }
+ if (NULL != pc->contract_terms)
+ {
+ json_decref (pc->contract_terms);
+ pc->contract_terms = NULL;
+ }
+ for (unsigned int i = 0; i<pc->coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->dc[i];
+
+ TALER_denom_sig_free (&dc->cdd.denom_sig);
+ GNUNET_free (dc->exchange_url);
+ }
+ GNUNET_free (pc->dc);
+ for (unsigned int i = 0; i<pc->num_exchanges; i++)
+ {
+ struct ExchangeGroup *eg = pc->egs[i];
+
+ if (NULL != eg->fo)
+ TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
+ GNUNET_free (eg);
+ }
+ GNUNET_free (pc->egs);
+ if (NULL != pc->response)
+ {
+ MHD_destroy_response (pc->response);
+ pc->response = NULL;
+ }
+ GNUNET_free (pc->fulfillment_url);
+ GNUNET_free (pc->session_id);
+ GNUNET_CONTAINER_DLL_remove (pc_head,
+ pc_tail,
+ pc);
+ GNUNET_free (pc->pos_key);
+ GNUNET_free (pc);
}
@@ -2743,59 +2636,76 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
struct TMH_HandlerContext *hc)
{
struct PayContext *pc = hc->ctx;
- enum GNUNET_GenericReturnValue ret;
GNUNET_assert (NULL != hc->infix);
if (NULL == pc)
{
pc = GNUNET_new (struct PayContext);
- GNUNET_CONTAINER_DLL_insert (pc_head,
- pc_tail,
- pc);
pc->connection = connection;
pc->hc = hc;
pc->order_id = hc->infix;
hc->ctx = pc;
hc->cc = &pay_context_cleanup;
- ret = parse_pay (pc);
- if (GNUNET_OK != ret)
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
- }
- if (GNUNET_SYSERR == pc->suspended)
- return MHD_NO; /* during shutdown, we don't generate any more replies */
- GNUNET_assert (GNUNET_NO == pc->suspended);
- if (0 != pc->response_code)
- {
- /* We are *done* processing the request, just queue the response (!) */
- if (UINT_MAX == pc->response_code)
+ GNUNET_CONTAINER_DLL_insert (pc_head,
+ pc_tail,
+ pc);
+ }
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing /pay in phase %d\n",
+ (int) pc->phase);
+ switch (pc->phase)
{
- GNUNET_break (0);
- return MHD_NO; /* hard error */
+ case PP_INIT:
+ phase_parse_pay (pc);
+ break;
+ case PP_CHECK_CONTRACT:
+ phase_check_contract (pc);
+ break;
+ case PP_CONTRACT_PAID:
+ phase_contract_paid (pc);
+ break;
+ case PP_PAY_TRANSACTION:
+ phase_execute_pay_transaction (pc);
+ break;
+ case PP_BATCH_DEPOSITS:
+ phase_batch_deposits (pc);
+ break;
+ case PP_PAYMENT_NOTIFICATION:
+ phase_payment_notification (pc);
+ break;
+ case PP_SUCCESS_RESPONSE:
+ phase_success_response (pc);
+ break;
+ case PP_RETURN_RESPONSE:
+ phase_return_response (pc);
+ break;
+ case PP_END_YES:
+ return MHD_YES;
+ case PP_END_NO:
+ return MHD_NO;
+ }
+ switch (pc->suspended)
+ {
+ case GNUNET_SYSERR:
+ /* during shutdown, we don't generate any more replies */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing /pay ends due to shutdown in phase %d\n",
+ (int) pc->phase);
+ return MHD_NO;
+ case GNUNET_NO:
+ /* continue to next phase */
+ break;
+ case GNUNET_YES:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing /pay suspended in phase %d\n",
+ (int) pc->phase);
+ return MHD_YES;
}
- return MHD_queue_response (connection,
- pc->response_code,
- pc->response);
}
- ret = check_contract (pc);
- if (GNUNET_OK != ret)
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
-
- /* Payment not finished, suspend while we interact with the exchange */
- MHD_suspend_connection (connection);
- pc->suspended = GNUNET_YES;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Suspending pay handling while working with the exchange\n");
- GNUNET_assert (NULL == pc->timeout_task);
- pc->timeout_task
- = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt),
- &handle_pay_timeout,
- pc);
- GNUNET_assert (NULL != pc->wm);
- execute_pay_transaction (pc);
+ /* impossible to get here */
+ GNUNET_assert (0);
return MHD_YES;
}