summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-05-21 23:06:59 +0200
committerChristian Grothoff <christian@grothoff.org>2020-05-21 23:06:59 +0200
commitfe19d9ada3f6dfdf3014b9610090b8750de32db8 (patch)
treec4bcdcca15c53bfbef46497892d99248036c7c49
parentd46bd871bcd34b9e0d633ab15fd9a4747861616c (diff)
downloadmerchant-fe19d9ada3f6dfdf3014b9610090b8750de32db8.tar.gz
merchant-fe19d9ada3f6dfdf3014b9610090b8750de32db8.tar.bz2
merchant-fe19d9ada3f6dfdf3014b9610090b8750de32db8.zip
towards POST tips pickup impl
-rw-r--r--src/backend/taler-merchant-httpd_post-tips-ID-pickup.c829
-rw-r--r--src/backend/taler-merchant-httpd_private-get-tips-ID.c94
-rw-r--r--src/include/taler_merchantdb_plugin.h149
3 files changed, 997 insertions, 75 deletions
diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
index 44258343..c24c2c05 100644
--- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
+++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
@@ -30,12 +30,467 @@
/**
+ * How often do we retry on serialization errors?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * How long do we give the exchange operation to complete withdrawing
+ * all of the planchets?
+ */
+#define EXCHANGE_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, 45)
+
+
+/**
+ * Active pickup operations.
+ */
+struct PickupContext;
+
+
+/**
+ * Handle for an individual planchet we are processing for a tip.
+ */
+struct PlanchetOperation
+{
+ /**
+ * Active pickup operation this planchet belongs with.
+ */
+ struct PickupContext *pc;
+
+ /**
+ * Find operation (while active), later NULL.
+ */
+ struct TMH_EXCHANGES_FindOperation *fo;
+
+ /**
+ * Withdraw handle (NULL while @e fo is active).
+ */
+ struct TALER_EXCHANGE_Withdraw2Handle *w2h;
+
+ /**
+ * Details about the planchet for withdrawing.
+ */
+ struct TALER_PlanchetDetail pd;
+
+ /**
+ * Offset of this planchet in the original request.
+ */
+ unsigned int offset;
+};
+
+
+/**
+ * Active pickup operations.
+ */
+struct PickupContext
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct PickupContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PickupContext *prev;
+
+ /**
+ * The connection.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Timeout task.
+ */
+ struct GNUNET_SCHEDULER_Task *tt;
+
+ /**
+ * Head of DLL of exchange operations on planchets.
+ */
+ struct PlanchetOperation *po_head;
+
+ /**
+ * Tail of DLL of exchange operations on planchets.
+ */
+ struct PlanchetOperation *po_tail;
+
+ /**
+ * HTTP response to return (set on errors).
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Find operation (while active), later NULL.
+ */
+ struct TMH_EXCHANGES_FindOperation *fo;
+
+ /**
+ * Which reserve are we draining?
+ */
+ struct TALER_ReservePrivateKey reserve_priv;
+
+ /**
+ * Which tip is being picked up?
+ */
+ struct GNUNET_HashCode tip_id;
+
+ /**
+ * Array of our planchets.
+ */
+ struct TALER_PlanchetDetail *planchets;
+
+ /**
+ * Length of the @e planchets array.
+ */
+ unsigned int planchets_length;
+
+ /**
+ * HTTP status to use (set on errors).
+ */
+ unsigned int http_status;
+
+ /**
+ * Total amount requested in the pick up operation. Computed by
+ * totaling up the amounts of all the @e planchets.
+ */
+ struct TALER_Amount total_requested;
+
+ /**
+ * True if @e total_requested has been initialized.
+ */
+ bool tr_initialized;
+};
+
+
+/**
+ * Head of DLL.
+ */
+static struct PickupContext *pc_head;
+
+/**
+ * Tail of DLL.
+ */
+static struct PickupContext *pc_tail;
+
+
+/**
+ * Stop all ongoing operations associated with @a pc.
+ */
+static void
+stop_operations (struct PickupContext *pc)
+{
+ struct PlanchetOperation *po;
+
+ if (NULL != pc->tt)
+ {
+ GNUNET_SCHEDULER_cancel (pc->tt);
+ pc->tt = NULL;
+ }
+ if (NULL != pc->fo)
+ {
+ TMH_EXCHANGES_find_exchange_cancel (pc->fo);
+ pc->fo = NULL;
+ }
+ while (NULL != (po = pc->po_head))
+ {
+ if (NULL != po->fo)
+ {
+ TMH_EXCHANGES_find_exchange_cancel (po->fo);
+ po->fo = NULL;
+ }
+ if (NULL != po->w2h)
+ {
+ TALER_EXCHANGE_withdraw2_cancel (po->w2h);
+ po->w2h = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (pc->po_head,
+ pc->po_tail,
+ po);
+ }
+}
+
+
+/**
+ * Function called to clean up.
+ *
+ * @param cls a `struct PickupContext *` to clean up
+ */
+static void
+pick_context_cleanup (void *cls)
+{
+ struct PickupContext *pc = cls;
+
+ stop_operations (pc); /* should not be any... */
+ for (unsigned int i = 0; i<pc->planchets_length; i++)
+ GNUNET_free (pc->planchets[i].coin_ev);
+ GNUNET_array_grow (pc->planchets,
+ pc->planchets_length,
+ 0);
+ GNUNET_free (pc);
+}
+
+
+/**
* We are shutting down, force resuming all suspended pickup operations.
*/
void
TMH_force_tip_pickup_resume ()
{
- // FIXME!
+ for (struct PickupContext *pc = pc_head;
+ NULL != pc;
+ pc = pc->next)
+ {
+ stop_operations (pc);
+ MHD_resume_connection (pc->connection);
+ }
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * withdraw request to a exchange without the (un)blinding factor.
+ * We persist the result in the database and, if we were the last
+ * planchet operation, resume HTTP processing.
+ *
+ * @param cls closure with a `struct PlanchetOperation *`
+ * @param hr HTTP response data
+ * @param blind_sig blind signature over the coin, NULL on error
+ */
+static void
+withdraw_cb (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ const struct GNUNET_CRYPTO_RsaSignature *blind_sig)
+{
+ struct PlanchetOperation *po = cls;
+ struct PickupContext *pc = po->pc;
+
+ GNUNET_CONTAINER_DLL_remove (pc->po_head,
+ pc->po_tail,
+ po);
+ if (NULL == blind_sig)
+ {
+ stop_operations (pc);
+ pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+ pc->response =
+ TALER_MHD_make_json_pack (
+ "{s:I, s:I, s:I, s:O}",
+ "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_ERROR,
+ "exchange_code", (json_int_t) hr->ec,
+ "exchange_http_status", (json_int_t) hr->http_status,
+ "exchange_reply", hr->reply);
+ MHD_resume_connection (pc->connection);
+ return;
+ }
+ qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
+ &pc->pickup_id,
+ po->offset,
+ blind_sig);
+ if (qs < 0)
+ {
+ stop_operations (pc);
+ pc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ pc->response = TALER_MHD_make_error (
+ TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+ "Could not store blind signature in DB");
+ MHD_resume_connection (pc->connection);
+ return;
+ }
+ if (NULL == pc->po_head)
+ {
+ stop_operations (pc); /* stops timeout job */
+ MHD_resume_connection (pc->connection);
+ }
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation as part of a withdraw objective. If the exchange is ready,
+ * withdraws the planchet from the exchange.
+ *
+ * @param cls closure, with our `struct PlanchetOperation *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+do_withdraw (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ struct TALER_EXCHANGE_Handle *eh,
+ const char *payto_uri,
+ const struct TALER_Amount *wire_fee,
+ bool exchange_trusted)
+{
+ struct PlanchetOperation *po = cls;
+ struct PickupContext *pc = po->pc;
+
+ po->fo = NULL;
+ if (NULL == eh)
+ {
+ stop_operations (pc);
+ GNUNET_CONTAINER_DLL_remove (pc->po_head,
+ pc->po_tail,
+ po);
+ pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+ pc->response =
+ TALER_MHD_make_json_pack (
+ "{s:I, s:I, s:I, s:O}",
+ "code", (json_int_t) TALER_EC_TIP_PICKUP_CONTACT_EXCHANGE_ERROR,
+ "exchange_code", (json_int_t) hr->ec,
+ "exchange_http_status", (json_int_t) hr->http_status,
+ "exchange_reply", hr->reply);
+ MHD_resume_connection (pc->connection);
+ return;
+ }
+ po->w2h = TALER_EXCHANGE_withdraw2 (eh,
+ &po->pd,
+ &pc->reserve_priv,
+ &withdraw_cb,
+ po);
+}
+
+
+/**
+ * Withdraw @a planchet from @a exchange_url for @a pc operation at planchet
+ * @a offset. Sets up the respective operation and adds it @a pc's operation
+ * list. Once the operation is complete, the resulting blind signature is
+ * committed to the merchant's database. If all planchet operations are
+ * completed, the HTTP processing is resumed.
+ *
+ * @param[in,out] pc a pending pickup operation that includes @a planchet
+ * @param exchange_url identifies an exchange to do the pickup from
+ * @param planchet details about the coin to pick up
+ * @param offset offset of @a planchet in the list, needed to process the reply
+ */
+static void
+try_withdraw (struct PickupContext *pc,
+ const char *exchange_url,
+ const struct TALER_PlanchetDetail *planchet,
+ unsigned int offset)
+{
+ struct PlanchetOperation *po;
+
+ po = GNUNET_new (struct PlanchetOperation);
+ po->pc = pc;
+ po->pd = *planchet;
+ po->offset = offset;
+ po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+ NULL,
+ GNUNET_NO,
+ &do_withdraw,
+ po);
+ GNUNET_assert (NULL != po->fo);
+ GNUNET_CONTAINER_DLL_insert (pc->fo_head,
+ pc->fo_tail,
+ fo);
+}
+
+
+/**
+ * Handle timeout for pickup.
+ *
+ * @param cls a `struct PickupContext *`
+ */
+static void
+do_timeout (void *cls)
+{
+ struct PickupContext *pc = cls;
+
+ pc->tt = NULL;
+ stop_operations (pc);
+ pc->http_status = MHD_HTTP_REQUEST_TIMEOUT;
+ pc->response = TALER_MHD_make_error (
+ TALER_EC_TIP_PICKUP_EXCHANGE_TIMEOUT,
+ "Timeout trying to withdraw from exchange (try again later)");
+ MHD_resume_connection (pc->connection);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation as part of a withdraw objective. Here, we initialize
+ * the "total_requested" amount by adding up the cost of the planchets
+ * provided by the client.
+ *
+ * @param cls closure, with our `struct PickupContext *`
+ * @param hr HTTP response details
+ * @param eh handle to the exchange context
+ * @param payto_uri payto://-URI of the exchange
+ * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
+ * @param exchange_trusted true if this exchange is trusted by config
+ */
+static void
+compute_total_requested (void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr,
+ struct TALER_EXCHANGE_Handle *eh,
+ const char *payto_uri,
+ const struct TALER_Amount *wire_fee,
+ bool exchange_trusted)
+{
+ struct PickupContext *pc = cls;
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ pc->fo = NULL;
+ stop_operations (pc); /* stops timeout job */
+ if ( (NULL == eh) ||
+ (NULL == (keys = TALER_EXCHANGE_get_keys (eh))) )
+ {
+ pc->http_status = MHD_HTTP_FAILED_DEPENDENCY;
+ pc->response =
+ TALER_MHD_make_json_pack (
+ "{s:I, s:I, s:I, s:O}",
+ "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_KEYS_ERROR,
+ "exchange_code", (json_int_t) hr->ec,
+ "exchange_http_status", (json_int_t) hr->http_status,
+ "exchange_reply", hr->reply);
+ MHD_resume_connection (pc->connection);
+ return;
+ }
+ TALER_amount_get_zero (TMH_currency,
+ &pc->total_requested);
+ for (unsigned int i = 0; i<pc->planchets_length; i++)
+ {
+ struct TALER_PlanchetDetail *pd = &pc->planchets[i];
+ const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+ dpk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+ &pd->denom_pub_hash);
+ if (NULL == dpk)
+ {
+ pc->http_status = MHD_HTTP_BAD_REQUEST;
+ pc->response =
+ TALER_MHD_make_json_pack (
+ "{s:I, s:I, s:I, s:O}",
+ "code", (json_int_t) TALER_EC_TIP_PICKUP_DENOMINATION_UNKNOWN,
+ "exchange_code", (json_int_t) hr->ec,
+ "exchange_http_status", (json_int_t) hr->http_status,
+ "exchange_reply", hr->reply);
+ MHD_resume_connection (pc->connection);
+ return;
+ }
+
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&pc->total_requested,
+ &dpk->value)) ||
+ (0 >
+ TALER_amount_add (&pc->total_requested,
+ &pc->total_requested,
+ &dpk->value)) )
+ {
+ pc->http_status = MHD_HTTP_BAD_REQUEST;
+ pc->response =
+ TALER_MHD_make_error (TALER_EC_TIP_PICKUP_SUMMATION_FAILED,
+ "Could not add up values to compute pickup total");
+ MHD_resume_connection (pc->connection);
+ return;
+ }
+ }
+ pc->tr_initialized = true;
+
+ return;
}
@@ -53,43 +508,271 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
+ struct PickupContext *pc = hc->ctx;
+
+ char *justification;
char *exchange_url;
- struct GNUNET_HashCode tip_id;
- struct TALER_Amount tip_amount;
- struct TALER_Amount tip_amount_left;
- struct GNUNET_TIME_Absolute timestamp;
+
+ struct TALER_Amount total_authorized;
+ struct TALER_Amount total_picked_up;
+ struct TALER_Amount total_remaining;
+ struct GNUNET_TIME_Absolute expiration;
struct GNUNET_TIME_Absolute timestamp_expire;
+ struct TALER_ReservePrivateKey reserve_priv;
MHD_RESULT ret;
enum GNUNET_DB_QueryStatus qs;
+ unsigned int num_coins;
+ unsigned int num_retries;
+
+ if (NULL == pc)
+ {
+ json_t *planchets;
+ json_t *planchet;
+ size_t index;
+
+ pc = GNUNET_new (struct PickupContext);
+ hc->ctx = pc;
+ hc->cc = &pick_context_cleanup;
+
+ GNUNET_assert (NULL != hc->infix);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_hash_from_string (hc->infix,
+ &pc->tip_id))
+ {
+ /* tip_id has wrong encoding */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_PARAMETER_MALFORMED,
+ "tip_id malformed");
+ }
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("planchets",
+ &planchets),
+ GNUNET_JSON_spec_end ()
+ };
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+ }
+ if (! json_is_array (planchets))
+ {
+ GNUNET_break_op (0);
+ json_decref (planchets);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_PATCH_INSTANCES_BAD_PAYTO_URIS,
+ "Invalid bank account information");
+ }
+
+ GNUNET_array_grow (pc->planchets,
+ pc->planchets_length,
+ json_array_size (planchets));
+ json_array_foreach (planchets, index, planchet) {
+ struct TALER_PlanchetDetail *pd = &pc->planchets[index];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &pd->denom_pub_hash),
+ GNUNET_JSON_spec_varsize ("coin_ev",
+ (void **) &pd->coin_ev,
+ &pd->coin_ev_size),
+ GNUNET_JSON_spec_end ()
+ };
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ planchet,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ json_decref (planchets);
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+ }
+ }
+ json_decref (planchets);
+ {
+ struct GNUNET_HashContext *hc;
+
+ hc = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_CRYPTO_hash_context_read (hc,
+ &pc->tip_id,
+ sizeof (pc->tip_id));
+ for (unsigned int i = 0; i<pc->planchets_length; i++)
+ {
+ GNUNET_CRYPTO_hash_context_read (hc,
+ &pd->denom_pub_hash,
+ sizeof (pd->denom_pub_hash));
+ GNUNET_CRYPTO_hash_context_read (hc,
+ pd->coin_ev,
+ pd->coin_ev_size);
+ }
+ GNUNET_CRYPTO_hash_context_finish (hc,
+ &pc->pickup_id);
+ }
+ }
+
+ if (NULL != pc->response)
+ {
+ MDH_RESULT ret;
+
+ ret = MHD_queue_response (connection,
+ pc->http_status,
+ pc->response);
+ pc->response = NULL;
+ return ret;
+ }
+
+ if (! pc->tr_initialized)
+ {
+ MHD_suspend_connection (connection);
+ pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+ &do_timeout,
+ pc);
+ pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+ NULL,
+ GNUNET_NO,
+ &compute_total_requested,
+ pc);
+ return MHD_YES;
+ }
+
- GNUNET_assert (NULL != hc->infix);
+ TMH_db->preflight (TMH_db->cls);
+ num_retries = 0;
+RETRY:
+ num_retries++;
+ if (num_retries > MAX_RETRIES)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_TIP_PICKUP_DB_ERROR_SOFT,
+ "Too many DB serialization failures");
+ }
if (GNUNET_OK !=
- GNUNET_CRYPTO_hash_from_string (hc->infix,
- &tip_id))
+ TMH_db->start (TMH_db->cls,
+ "pickup tip"))
{
- /* tip_id has wrong encoding */
- GNUNET_break_op (0);
+ GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PARAMETER_MALFORMED,
- "tip_id malformed");
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_TIP_PICKUP_DB_ERROR_HARD,
+ "Could not begin transaction");
}
+ {
+ struct GNUNET_CRYPTO_RsaSignature *sigs[num_coins];
+
+ qs = TMH_db->lookup_pickup (TMH_db->cls,
+ hc->instance->settings.id,
+ &tip_id,
+ &pickup_id,
+ &exchange_url,
+ &pc->reserve_priv;
+ num_coins,
+ sigs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ for (unsigned int i = 0; i<num_coins; i++)
+ {
+ if (NULL == sigs[i])
+ {
+ try_withdraw (pc,
+ exchange_url,
+ planchets[i],
+ i);
+ qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ MHD_suspend_connection (connection);
+ GNUNET_CONTAINER_DLL_insert (pc_head,
+ pc_tail,
+ pc);
+ pc->tt = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
+ &do_timeout,
+ pc);
+ TMH_db->rollback (TMH_db->cls);
+ return MHD_YES;
+ }
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ {
+ unsigned int response_code;
+ enum TALER_ErrorCode ec;
- db->preflight (db->cls);
- // FIXME: logic here is completely bonkers!
- qs = db->lookup_tip_by_id (db->cls,
- &tip_id,
- &exchange_url,
- &extra,
- &tip_amount,
- &tip_amount_left,
- &timestamp);
+ TMH_db->rollback (TMH_db->cls);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ json_t *blind_sigs;
+
+ blind_sigs = json_array ();
+ GNUNET_assert (NULL != blind_sigs);
+ for (unsigned int i = 0; i<num_coins; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ blind_sigs,
+ GNUNET_JSON_from_rsa_signature (sigs[i])));
+ GNUNET_CRYPTO_rsa_signature_free (sigs[i]);
+ }
+ return TALER_MHD_reply_json_pack (
+ connection,
+ MHD_HTTP_OK,
+ "{s:o}",
+ "blind_sigs", blind_sigs);
+ }
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto RETRY;
+ break;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ ec = TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
+ response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ default:
+ GNUNET_break (0);
+ ec = TALER_EC_INTERNAL_LOGIC_ERROR;
+ response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+ return TALER_MHD_reply_with_error (connection,
+ response_code,
+ ec,
+ "Could not process pickup");
+ }
+ }
+ qs = TMH_db->lookup_tip (TMH_db->cls,
+ hc->instance->settings.id,
+ &tip_id,
+ &total_authorized,
+ &total_picked_up,
+ &expiration,
+ &exchange_url,
+ &pc->reserve_priv);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
unsigned int response_code;
enum TALER_ErrorCode ec;
+ TMH_db->rollback (TMH_db->cls);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
@@ -97,9 +780,7 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
response_code = MHD_HTTP_NOT_FOUND;
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
- ec = TALER_EC_TIP_PICKUP_DB_ERROR_SOFT;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
+ goto RETRY;
case GNUNET_DB_STATUS_HARD_ERROR:
ec = TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
@@ -113,24 +794,82 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler *rh,
return TALER_MHD_reply_with_error (connection,
response_code,
ec,
- "Could not determine exchange URL for the given tip id");
- }
-
- timestamp_expire = GNUNET_TIME_absolute_add (timestamp,
- GNUNET_TIME_UNIT_DAYS);
-
- ret = TALER_MHD_reply_json_pack (
- connection,
- MHD_HTTP_OK,
- "{s:s, s:o, s:o, s:o, s:o, s:o}",
- "exchange_url", exchange_url,
- "amount", TALER_JSON_from_amount (&tip_amount),
- "amount_left", TALER_JSON_from_amount (&tip_amount_left),
- "stamp_created", GNUNET_JSON_from_time_abs (timestamp),
- "stamp_expire", GNUNET_JSON_from_time_abs (timestamp_expire),
- "extra", extra);
-
- GNUNET_free (exchange_url);
- json_decref (extra);
- return ret;
+ "Could not process pickup");
+ }
+ if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ return TALER_MHD_reply_with_error (connection,
+ response_code,
+ ec,
+ "Could not process pickup");
+ }
+ if (0 >
+ TALER_amount_subtract (&total_left,
+ &total_authorized,
+ &total_picked_up))
+ {
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_LOGIC_ERROR,
+ "tip already overdrawn");
+ }
+
+ if (0 >
+ TALER_amount_cmp (&total_left,
+ &total_requested))
+ {
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_PICKUP_AMOUNT_EXCEEDS_TIP_REMAINING,
+ "requested amount exceeds amount left in tip");
+ }
+
+ GNUNET_assert (0 >
+ TALER_amount_add (&total_picked_up,
+ &total_picked_up,
+ &total_requested));
+ qs = TMH_db->insert_pickup (TMH_db->cls,
+ hc->instance->settings.id,
+ &tip_id,
+ &total_picked_up,
+ &pickup_id,
+ &total_requested);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto RETRY;
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+ "Could not store pickup ID in database");
+ }
+ qs = TMH_db->commit (TMH_db->cls);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto RETRY;
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_TIP_PICKUP_DB_STORE_HARD_ERROR,
+ "Could not commit transaction to database");
+ }
+ MHD_suspend_connection (connection);
+ pc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+ &do_timeout,
+ pc);
+ for (unsigned int i = 0; i<num_coins; i++)
+ {
+ try_withdraw (pc,
+ exchange_url,
+ planchets[i],
+ i);
+ }
+ return MHD_YES;
}
diff --git a/src/backend/taler-merchant-httpd_private-get-tips-ID.c b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
index f186363d..6276189f 100644
--- a/src/backend/taler-merchant-httpd_private-get-tips-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-tips-ID.c
@@ -42,14 +42,17 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
- char *exchange_url;
struct GNUNET_HashCode tip_id;
- struct TALER_Amount tip_amount;
- struct TALER_Amount tip_amount_left;
- struct GNUNET_TIME_Absolute timestamp;
+ struct TALER_Amount total_authorized;
+ struct TALER_Amount total_picked_up;
+ char *reason;
+ struct GNUNET_TIME_Absolute expiration;
+ unsigned int pickups_length = 0;
+ struct TALER_MERCHANTDB_PickupDetails *pickups = NULL;
struct GNUNET_TIME_Absolute timestamp_expire;
- MHD_RESULT ret;
enum GNUNET_DB_QueryStatus qs;
+ bool fpu;
+ json_t *pickups_json;
GNUNET_assert (NULL != hc->infix);
if (GNUNET_OK !=
@@ -63,17 +66,27 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
TALER_EC_PARAMETER_MALFORMED,
"tip_id malformed");
}
+ {
+ const char *pstr;
+ pstr = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "pickups");
+ fpu = (NULL != pstr) ? 0 == strcasecmp (pstr, "yes") :
+ false;
+ }
db->preflight (db->cls);
- // FIXME: logic here is completely bonkers!
- qs = db->lookup_tip_by_id (db->cls,
- &tip_id,
- &exchange_url,
- &extra,
- &tip_amount,
- &tip_amount_left,
- &timestamp);
-
+ qs = db->lookup_tip_details (db->cls,
+ hc->instance->settings.id,
+ &tip_id,
+ fpu,
+ &total_authorized,
+ &total_picked_up,
+ &reason,
+ &expiration,
+ &reserve_pub,
+ &pickups_length,
+ &pickups);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
unsigned int response_code;
@@ -104,21 +117,42 @@ TMH_private_get_tips_ID (const struct TMH_RequestHandler *rh,
ec,
"Could not determine exchange URL for the given tip id");
}
+ if (fpu)
+ {
+ pickups_json = json_array ();
+ GNUNET_assert (NULL != pickups_json);
+ for (unsigned int i = 0; i<pickups_length; i++)
+ {
+ json_array_append_new (
+ pickups_json,
+ json_pack ("{s:o,s:o,s:o,s:I}",
+ "pickup_id",
+ GNUNET_JSON_from_data_auto (&pickups[i].pickup_id),
+ "requested_amount",
+ TALER_JSON_from_amount (&pickups[i].requested_amount),
+ "exchange_amount",
+ TALER_JSON_from_amount (&pickups[i].exchange_amount),
+ "num_planchets",
+ (json_int_t) pickups[i].num_planchets));
+ }
+ }
+ GNUNET_array_grow (pickups,
+ pickups_length,
+ 0);
+ {
+ MHD_RESULT ret;
- timestamp_expire = GNUNET_TIME_absolute_add (timestamp,
- GNUNET_TIME_UNIT_DAYS);
-
- ret = TALER_MHD_reply_json_pack (
- connection,
- MHD_HTTP_OK,
- "{s:s, s:o, s:o, s:o, s:o, s:o?}",
- "reason", reason,
- "total_authorized", TALER_JSON_from_amount (&tip_amount),
- "total_picked_up", TALER_JSON_from_amount (&tip_amount_left),
- "reserve_pub", GNUNET_JSON_from_data_auto (&reserve_pub),
- "expiration", GNUNET_JSON_from_time_abs (timestamp_expire),
- "pickups", pickups);
-
- GNUNET_free (exchange_url);
- return ret;
+ ret = TALER_MHD_reply_json_pack (
+ connection,
+ MHD_HTTP_OK,
+ "{s:s, s:o, s:o, s:o, s:o, s:o?}",
+ "reason", reason,
+ "total_authorized", TALER_JSON_from_amount (&total_authorized),
+ "total_picked_up", TALER_JSON_from_amount (&total_picked_up),
+ "reserve_pub", GNUNET_JSON_from_data_auto (&reserve_pub),
+ "expiration", GNUNET_JSON_from_time_abs (expiration),
+ "pickups", pickups_json);
+ GNUNET_free (reason);
+ return ret;
+ }
}
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index 0960fb93..60bf7778 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -635,6 +635,34 @@ typedef void
/**
+ * Details about a pickup operation executed by the merchant.
+ */
+struct TALER_MERCHANTDB_PickupDetails
+{
+ /**
+ * Identifier for the pickup operation.
+ */
+ struct GNUNET_HashCode pickup_id;
+
+ /**
+ * Total amount requested for this @e pickup_id.
+ */
+ struct TALER_Amount requested_amount;
+
+ /**
+ * Total amount we successfully obtained from the exchange.
+ */
+ struct TALER_Amount exchange_amount;
+
+ /**
+ * Number of planchets involved in the request.
+ */
+ unsigned int num_planchets;
+
+};
+
+
+/**
* Handle to interact with the database.
*
* Functions ending with "_TR" run their OWN transaction scope
@@ -1662,6 +1690,127 @@ struct TALER_MERCHANTDB_Plugin
struct GNUNET_TIME_Absolute *expiration);
+ /**
+ * Lookup pickup details for pickup @a pickup_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param pickup_id which pickup should we lookup details on
+ * @param[out] exchange_url which exchange is the tip withdrawn from
+ * @param[out] reserve_priv private key the tip is withdrawn from (set if still available!)
+ * @param sigs_length length of the @a sigs array
+ * @param[out] sigs set to the (blind) signatures we have for this @a pickup_id,
+ * those that are unavailable are left at NULL
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_pickup)(void *cls,
+ const char *instance_id,
+ const struct GNUNET_HashCode *tip_id,
+ const struct GNUNET_HashCode *pickup_id,
+ char **exchange_url,
+ struct TALER_ReservePrivateKeyP *reserve_priv,
+ unsigned int sigs_length,
+ struct GNUNET_CRYPTO_RsaSignature *sigs[]);
+
+
+ /**
+ * Lookup tip details for tip @a tip_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param[out] total_authorized amount how high is the tip (with fees)
+ * @param[out] total_picked_up how much of the tip was so far picked up (with fees)
+ * @param[out] expiration set to when the tip expires
+ * @param[out] exchange_url set to the exchange URL where the reserve is
+ * @param[out] reserve_priv set to private key of reserve to be debited
+ * @return transaction status,
+ * #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired
+ * #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+ * #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left
+ * #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+ * #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client should retry)
+ * #TALER_EC_NONE upon success
+ */
+ enum TALER_ErrorCode
+ (*lookup_tip)(void *cls,
+ const char *instance_id,
+ const struct GNUNET_HashCode *tip_id,
+ struct TALER_Amount *total_authorized,
+ struct TALER_Amount *total_picked_up,
+ struct GNUNET_TIME_Absolute *expiration,
+ char **exchange_url,
+ struct TALER_ReservePrivateKeyP *reserve_priv);
+
+
+ /**
+ * Lookup tip details for tip @a tip_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param[out] total_authorized amount how high is the tip (with fees)
+ * @param[out] total_picked_up how much of the tip was so far picked up (with fees)
+ * @param[out] expiration set to when the tip expires
+ * @param[out] exchange_url set to the exchange URL where the reserve is
+ * @param[out] reserve_priv set to private key of reserve to be debited
+ * @return transaction status,
+ * #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired
+ * #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+ * #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left
+ * #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+ * #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client should retry)
+ * #TALER_EC_NONE upon success
+ */
+ enum TALER_ErrorCode
+ (*lookup_tip)(void *cls,
+ const char *instance_id,
+ const struct GNUNET_HashCode tip_id,
+ struct TALER_Amount *total_authorized,
+ struct TALER_Amount *total_picked_up,
+ struct GNUNET_TIME_Absolute *expiration,
+ char **exchange_url,
+ struct TALER_ReservePrivateKeyP *reserve_priv);
+
+
+ /**
+ * Lookup tip details for tip @a tip_id.
+ *
+ * @param cls closure, typically a connection to the db
+ * @param instance_id which instance should we lookup tip details for
+ * @param tip_id which tip should we lookup details on
+ * @param fpu should we fetch details about individual pickups
+ * @param[out] total_authorized amount how high is the tip (with fees)
+ * @param[out] total_picked_up how much of the tip was so far picked up (with fees)
+ * @param[out] justification why was the tip approved
+ * @param[out] expiration set to when the tip expires
+ * @param[out] reserve_pub set to which reserve is debited
+ * @param[out] pickups_length set to the length of @e pickups
+ * @param[out] pickups if @a fpu is true, set to details about the pickup operations
+ * @return transaction status,
+ * #TALER_EC_TIP_AUTHORIZE_RESERVE_EXPIRED if the reserve is known but has expired
+ * #TALER_EC_TIP_AUTHORIZE_RESERVE_UNKNOWN if the reserve is not known
+ * #TALER_EC_TIP_AUTHORIZE_INSUFFICIENT_FUNDS if the reserve has insufficient funds left
+ * #TALER_EC_TIP_AUTHORIZE_DB_HARD_ERROR on hard DB errors
+ * #TALER_EC_TIP_AUTHORIZE_DB_SOFT_ERROR on soft DB errors (client should retry)
+ * #TALER_EC_NONE upon success
+ */
+ enum TALER_ErrorCode
+ (*lookup_tip_details)(void *cls,
+ const char *instance_id,
+ const struct GNUNET_HashCode tip_id,
+ bool fpu,
+ struct TALER_Amount *total_authorized,
+ struct TALER_Amount *total_picked_up,
+ char **justification,
+ struct GNUNET_TIME_Absolute *expiration,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ unsigned int *pickups_length,
+ struct TALER_MERCHANTDB_PickupDetails **pickups);
+
+
/* ****************** OLD API ******************** */