summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorChristian Grothoff <grothoff@gnunet.org>2023-11-27 13:10:30 +0900
committerChristian Grothoff <grothoff@gnunet.org>2023-11-27 13:10:30 +0900
commit5f97f9112746afa308d6b281cbefad4b6e166370 (patch)
tree537f4d9f71779bd88eca33842723b1a99392cd06 /src/backend
parent7782a680228b6e76ae2598cacfc65ff250e8e020 (diff)
downloadmerchant-5f97f9112746afa308d6b281cbefad4b6e166370.tar.gz
merchant-5f97f9112746afa308d6b281cbefad4b6e166370.tar.bz2
merchant-5f97f9112746afa308d6b281cbefad4b6e166370.zip
major /pay handling cleanup
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c1392
1 files changed, 778 insertions, 614 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 c942847f..e263538a 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -59,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
@@ -228,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;
@@ -392,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
@@ -404,13 +466,13 @@ struct PayContext
* does not match the contract currency.
*/
bool deposit_currency_mismatch;
-
+
/**
* Set to true if the database contains a (bogus)
* refund for a different currency.
*/
bool refund_currency_mismatch;
-
+
};
@@ -526,29 +588,6 @@ destroy_kc (struct KycContext *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;
-}
-
-
void
TMH_force_pc_resume ()
{
@@ -580,6 +619,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.
@@ -603,10 +657,8 @@ resume_pay_with_response (struct PayContext *pc,
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);
}
@@ -631,66 +683,44 @@ resume_pay_with_error (struct PayContext *pc,
/**
- * Custom cleanup routine for a `struct PayContext`.
+ * Conclude payment processing for @a pc with the
+ * given @a res MHD status code.
*
- * @param cls the `struct PayContext` to clean up.
+ * @param[in,out] pc payment context for final state transition
+ * @param res MHD return code to end with
*/
static void
-pay_context_cleanup (void *cls)
+pay_end (struct PayContext *pc,
+ MHD_RESULT res)
{
- 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;
- }
- 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);
+ pc->phase = (MHD_YES == res)
+ ? PP_END_YES
+ : PP_END_NO;
}
/**
- * Execute the DB transaction. If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Return response stored in @a pc.
*
- * @param pc payment context to transact
+ * @param[in,out] pc payment context we are processing
*/
static void
-execute_pay_transaction (struct PayContext *pc);
+phase_return_response (struct PayContext *pc)
+{
+ 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);
+ pay_end (pc,
+ MHD_NO); /* hard error */
+ return;
+ }
+ pay_end (pc,
+ MHD_queue_response (pc->connection,
+ pc->response_code,
+ pc->response));
+}
/**
@@ -1113,7 +1143,10 @@ batch_deposit_cb (
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,
@@ -1232,23 +1265,7 @@ process_pay_with_keys (
NULL);
return;
}
- eg->tried_force_keys = true;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Forcing /keys download (once) as wire method seems unsupported for debit\n");
- eg->fo = TMH_EXCHANGES_keys4exchange (
- eg->exchange_url,
- true,
- &process_pay_with_keys,
- eg);
- if (NULL == eg->fo)
- {
- GNUNET_break (0);
- pc->pending_at_eg--;
- resume_pay_with_error (pc,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
- "Failed to lookup exchange by URL");
- return;
- }
+ force_keys (eg);
return;
}
@@ -1421,7 +1438,11 @@ AGE_FAIL:
"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;
}
@@ -1510,13 +1531,56 @@ force_keys (struct ExchangeGroup *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: 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++)
{
@@ -1548,15 +1612,120 @@ start_batch_deposits (struct PayContext *pc)
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");
+ 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 GNUNET_CRYPTO_EddsaSignature 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;
}
@@ -1707,9 +1876,11 @@ check_payment_sufficient (struct PayContext *pc)
&pc->egs[i]->wire_fee))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- 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;
}
if (0 >
@@ -1718,9 +1889,12 @@ check_payment_sufficient (struct PayContext *pc)
&pc->egs[i]->wire_fee))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
- "could not add exchange wire fee to total");
+ 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;
}
}
@@ -1748,9 +1922,12 @@ check_payment_sufficient (struct PayContext *pc)
&dc->cdd.amount)) )
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- TALER_EC_GENERIC_CURRENCY_MISMATCH,
- dc->deposit_fee.currency);
+ 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 >
@@ -1764,9 +1941,12 @@ check_payment_sufficient (struct PayContext *pc)
{
GNUNET_break (0);
/* Overflow in these amounts? Very strange. */
- resume_pay_with_error (pc,
- 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 ==
@@ -1774,13 +1954,15 @@ check_payment_sufficient (struct PayContext *pc)
&dc->cdd.amount))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- 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;
}
-
- } /* deposit loop */
+ } /* end deposit loop */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Amount received from wallet: %s\n",
@@ -1805,9 +1987,12 @@ check_payment_sufficient (struct PayContext *pc)
&acc_fee))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- 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;
}
@@ -1818,9 +2003,12 @@ check_payment_sufficient (struct PayContext *pc)
&total_wire_fee))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- 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,
@@ -1845,9 +2033,12 @@ check_payment_sufficient (struct PayContext *pc)
&pc->amount))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- 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;
}
}
@@ -1868,9 +2059,12 @@ check_payment_sufficient (struct PayContext *pc)
&pc->total_refunded))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- 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;
}
@@ -1882,25 +2076,33 @@ check_payment_sufficient (struct PayContext *pc)
&total_needed))
{
GNUNET_break_op (0);
- resume_pay_with_error (pc,
- 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,
- 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,
- TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
- "payment insufficient");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ 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)"));
+ return false;
}
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
+ "payment insufficient"));
return false;
}
return true;
@@ -1908,97 +2110,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;
- 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);
- resume_pay_with_response (
- pc,
- MHD_HTTP_OK,
- TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("pos_confirmation",
- pos_confirmation)),
- GNUNET_JSON_pack_data_auto ("sig",
- &sig)));
- GNUNET_free (pos_confirmation);
-}
-
-
-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;
@@ -2007,12 +2126,13 @@ execute_pay_transaction (struct PayContext *pc)
if (pc->retry_counter++ > MAX_RETRIES)
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- 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()
@@ -2037,9 +2157,11 @@ execute_pay_transaction (struct PayContext *pc)
"run pay"))
{
GNUNET_break (0);
- resume_pay_with_error (pc,
- 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;
}
@@ -2056,28 +2178,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,
- 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);
- resume_pay_with_error (pc,
- TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- pc->amount.currency);
+ 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;
@@ -2091,23 +2213,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,
- 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);
- resume_pay_with_error (pc,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "refund currency in database does not match order currency");
+ 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;
}
}
@@ -2117,11 +2240,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;
}
@@ -2149,14 +2271,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,
- 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;
}
}
@@ -2184,14 +2305,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,
- 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;
}
}
@@ -2205,201 +2325,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,
- 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;
- 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 ()
- };
-
- {
- 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 ("");
- }
- pc->coins_cnt = json_array_size (coins);
- if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
- {
- GNUNET_break_op (0);
- 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_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);
- 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;
-
- /* Check the consistency of the (potential) age restriction
- * information. */
- if (dc->no_age_commitment != dc->no_minimum_age_sig)
- {
- GNUNET_break_op (0);
- 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,
- 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);
- }
- }
}
- return GNUNET_OK;
+ pc->phase = PP_PAYMENT_NOTIFICATION;
}
@@ -2456,11 +2392,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;
@@ -2473,13 +2407,12 @@ 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++)
{
@@ -2499,14 +2432,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,
@@ -2543,29 +2475,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;
@@ -2592,23 +2520,21 @@ 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) */
if (GNUNET_OK !=
@@ -2616,20 +2542,20 @@ check_contract (struct PayContext *pc)
&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",
@@ -2642,13 +2568,12 @@ 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 */
@@ -2691,7 +2616,11 @@ 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;
}
}
@@ -2700,10 +2629,12 @@ check_contract (struct PayContext *pc)
&pc->amount))
{
GNUNET_break (0);
- return 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");
+ 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 (unsigned int i=0;i<pc->coins_cnt;i++)
@@ -2715,13 +2646,12 @@ check_contract (struct PayContext *pc)
&pc->amount))
{
GNUNET_break_op (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- pc->amount.currency))
- ? GNUNET_NO
- : GNUNET_SYSERR;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ pc->amount.currency));
+ return;
}
}
@@ -2731,21 +2661,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 */
@@ -2759,35 +2690,251 @@ 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;
+}
+
- return GNUNET_OK;
+/**
+ * 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);
+ }
+ 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;
+
+ /* 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,
- 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);
}
@@ -2797,59 +2944,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;
}