/*
This file is part of TALER
(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
published by the Free Software Foundation; either version 3,
or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public
License along with TALER; see the file COPYING. If not,
see
*/
/**
* @file taler-merchant-httpd_private-post-orders.c
* @brief the POST /orders handler
* @author Christian Grothoff
* @author Marcello Stanisci
* @author Christian Blättler
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"
#include "taler_merchantdb_plugin.h"
/**
* How often do we retry the simple INSERT database transaction?
*/
#define MAX_RETRIES 3
/**
* Maximum number of inventory products per order.
*/
#define MAX_PRODUCTS 1024
/**
* What is the label under which we find/place the merchant's
* jurisdiction in the locations list by default?
*/
#define STANDARD_LABEL_MERCHANT_JURISDICTION "_mj"
/**
* What is the label under which we find/place the merchant's
* address in the locations list by default?
*/
#define STANDARD_LABEL_MERCHANT_ADDRESS "_ma"
/**
* How long do we wait at most for /keys from the exchange(s)?
* Ensures that we do not block forever just because some exchange
* fails to respond *or* because our taler-merchant-keyscheck
* refuses a forced download.
*/
#define MAX_KEYS_WAIT \
GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
/**
* Generate the base URL for the given merchant instance.
*
* @param connection the MHD connection
* @param instance_id the merchant instance ID
* @returns the merchant instance's base URL
*/
static char *
make_merchant_base_url (struct MHD_Connection *connection,
const char *instance_id)
{
struct GNUNET_Buffer buf;
if (GNUNET_OK !=
TMH_base_url_by_connection (connection,
instance_id,
&buf))
return NULL;
GNUNET_buffer_write_path (&buf,
"");
return GNUNET_buffer_reap_str (&buf);
}
/**
* Information about a product we are supposed to add to the order
* based on what we know it from our inventory.
*/
struct InventoryProduct
{
/**
* Identifier of the product in the inventory.
*/
const char *product_id;
/**
* Number of units of the product to add to the order.
*/
uint32_t quantity;
};
/**
* Handle for a rekey operation where we (re)request
* the /keys from the exchange.
*/
struct RekeyExchange
{
/**
* Kept in a DLL.
*/
struct RekeyExchange *prev;
/**
* Kept in a DLL.
*/
struct RekeyExchange *next;
/**
* order this is for.
*/
struct OrderContext *oc;
/**
* Base URL of the exchange.
*/
char *url;
/**
* Request for keys.
*/
struct TMH_EXCHANGES_KeysOperation *fo;
};
/**
* Information we keep per order we are processing.
*/
struct OrderContext
{
/**
* Information set in the ORDER_PHASE_PARSE_REQUEST phase.
*/
struct
{
/**
* Order field of the request
*/
json_t *order;
/**
* Set to how long refunds will be allowed.
*/
struct GNUNET_TIME_Relative refund_delay;
/**
* RFC8905 payment target type to find a matching merchant account
*/
const char *payment_target;
/**
* Shared key to use with @e pos_algorithm.
*/
const char *pos_key;
/**
* Selected algorithm (by template) when we are to
* generate an OTP code for payment confirmation.
*/
enum TALER_MerchantConfirmationAlgorithm pos_algorithm;
/**
* Hash of the POST request data, used to detect
* idempotent requests.
*/
struct TALER_MerchantPostDataHashP h_post_data;
/**
* Length of the @e inventory_products array.
*/
unsigned int inventory_products_length;
/**
* Specifies that some products are to be included in the
* order from the inventory. For these inventory management
* is performed (so the products must be in stock).
*/
struct InventoryProduct *inventory_products;
/**
* Length of the @e uuids array.
*/
unsigned int uuids_length;
/**
* array of UUIDs used to reserve products from @a inventory_products.
*/
struct GNUNET_Uuid *uuids;
/**
* Claim token for the request.
*/
struct TALER_ClaimTokenP claim_token;
/**
* Session ID (optional) to use for the order.
*/
const char *session_id;
} parse_request;
/**
* Information set in the ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
*/
struct
{
/**
* Wire method (and our bank account) we have selected
* to be included for this order.
*/
const struct TMH_WireMethod *wm;
} add_payment_details;
/**
* Information set in the ORDER_PHASE_PARSE_ORDER phase.
*/
struct
{
/**
* Version of the contract terms.
*/
enum TALER_MerchantContractVersion version;
/**
* Our order ID.
*/
const char *order_id;
/**
* Summary of the contract.
*/
const char *summary;
/**
* Internationalized summary.
*/
json_t *summary_i18n;
/**
* URL that will show that the contract was successful
* after it has been paid for.
*/
const char *fulfillment_url;
/**
* Message shown to the customer after paying for the contract.
* Either fulfillment_url or fulfillment_message must be specified.
*/
const char *fulfillment_message;
/**
* Map from IETF BCP 47 language tags to localized fulfillment messages.
*/
json_t *fulfillment_message_i18n;
/**
* Array of products that are part of the purchase.
*/
const json_t *products;
/**
* URL where the same contract could be ordered again (if available).
*/
const char *public_reorder_url;
/**
* Array of contract choices. Is null for v0 contracts.
*/
const json_t *choices;
/**
* Merchant base URL.
*/
char *merchant_base_url;
/**
* Timestamp of the order.
*/
struct GNUNET_TIME_Timestamp timestamp;
/**
* Deadline for refunds.
*/
struct GNUNET_TIME_Timestamp refund_deadline;
/**
* Payment deadline.
*/
struct GNUNET_TIME_Timestamp pay_deadline;
/**
* Wire transfer deadline.
*/
struct GNUNET_TIME_Timestamp wire_deadline;
/**
* Delivery date.
*/
struct GNUNET_TIME_Timestamp delivery_date;
/**
* Delivery location.
*/
json_t *delivery_location;
/**
* Gross amount value of the contract. Used to
* compute @e max_stefan_fee.
*/
struct TALER_Amount brutto;
/**
* Maximum fee as given by the client request.
*/
struct TALER_Amount max_fee;
/**
* Specifies for how long the wallet should try to get an
* automatic refund for the purchase.
*/
struct GNUNET_TIME_Relative auto_refund;
/**
* Nonce generated by the wallet and echoed by the merchant
* in this field when the proposal is generated.
*/
const char *nonce;
/**
* Extra data that is only interpreted by the merchant frontend.
*/
const json_t *extra;
/**
* Minimum age required by the order.
*/
uint32_t minimum_age;
} parse_order;
/**
* Information set in the ORDER_PHASE_PARSE_CHOICES phase.
*/
struct
{
/**
* Array of possible specific contracts the wallet/customer may choose
* from by selecting the respective index when signing the deposit
* confirmation.
*/
struct TALER_MerchantContractChoice *choices;
/**
* Length of the @e choices array.
*/
unsigned int choices_len;
/**
* Array of token families referenced in the contract.
*/
struct TALER_MerchantContractTokenFamily *token_families;
/**
* Length of the @e token_families array.
*/
unsigned int token_families_len;
} parse_choices;
/**
* Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
*/
struct
{
/**
* Merged array of products in the @e order.
*/
json_t *products;
} merge_inventory;
/**
* Information set in the ORDER_PHASE_SET_EXCHANGES phase.
*/
struct
{
/**
* Array of exchanges we find acceptable for this
* order.
*/
json_t *exchanges;
/**
* Forced requests to /keys to update our exchange
* information.
*/
struct RekeyExchange *pending_reload_head;
/**
* Forced requests to /keys to update our exchange
* information.
*/
struct RekeyExchange *pending_reload_tail;
/**
* Did we previously force reloading of /keys from
* all exchanges? Set to 'true' to prevent us from
* doing it again (and again...).
*/
bool forced_reload;
/**
* Set to true once we are sure that we have at
* least one good exchange.
*/
bool exchange_good;
/**
* Maximum fee for @e order based on STEFAN curves.
* Used to set @e max_fee if not provided as part of
* @e order.
*/
struct TALER_Amount max_stefan_fee;
/**
* Maximum amount that could be paid over all
* available exchanges. Used to determine if this
* order creation requests exceeds legal limits.
*/
struct TALER_Amount total_exchange_limit;
/**
* How long do we wait at most until giving up on getting keys?
*/
struct GNUNET_TIME_Absolute keys_timeout;
/**
* Task to wake us up on @e keys_timeout.
*/
struct GNUNET_SCHEDULER_Task *wakeup_task;
} set_exchanges;
/**
* Information set in the ORDER_PHASE_SET_MAX_FEE phase.
*/
struct
{
/**
* Maximum fee
*/
struct TALER_Amount max_fee;
} set_max_fee;
/**
* Information set in the ORDER_PHASE_EXECUTE_ORDER phase.
*/
struct
{
/**
* Which product (by offset) is out of stock, UINT_MAX if all were in-stock.
*/
unsigned int out_of_stock_index;
/**
* Set to a previous claim token *if* @e idempotent
* is also true.
*/
struct TALER_ClaimTokenP token;
/**
* Set to true if the order was idempotent and there
* was an equivalent one before.
*/
bool idempotent;
/**
* Set to true if the order is in conflict with a
* previous order with the same order ID.
*/
bool conflict;
} execute_order;
struct
{
/**
* Contract terms to store in the database.
*/
json_t *contract;
} serialize_order;
/**
* Connection of the request.
*/
struct MHD_Connection *connection;
/**
* Kept in a DLL while suspended.
*/
struct OrderContext *next;
/**
* Kept in a DLL while suspended.
*/
struct OrderContext *prev;
/**
* Handler context for the request.
*/
struct TMH_HandlerContext *hc;
/**
* #GNUNET_YES if suspended.
*/
enum GNUNET_GenericReturnValue suspended;
/**
* Current phase of setting up the order.
*/
enum
{
ORDER_PHASE_PARSE_REQUEST,
ORDER_PHASE_PARSE_ORDER,
ORDER_PHASE_PARSE_CHOICES,
ORDER_PHASE_MERGE_INVENTORY,
ORDER_PHASE_ADD_PAYMENT_DETAILS,
ORDER_PHASE_SET_EXCHANGES,
ORDER_PHASE_SET_MAX_FEE,
ORDER_PHASE_SERIALIZE_ORDER,
ORDER_PHASE_SALT_FORGETTABLE,
ORDER_PHASE_CHECK_CONTRACT,
ORDER_PHASE_EXECUTE_ORDER,
/**
* Processing is done, we should return #MHD_YES.
*/
ORDER_PHASE_FINISHED_MHD_YES,
/**
* Processing is done, we should return #MHD_NO.
*/
ORDER_PHASE_FINISHED_MHD_NO
} phase;
};
/**
* Kept in a DLL while suspended.
*/
static struct OrderContext *oc_head;
/**
* Kept in a DLL while suspended.
*/
static struct OrderContext *oc_tail;
void
TMH_force_orders_resume ()
{
struct OrderContext *oc;
while (NULL != (oc = oc_head))
{
GNUNET_CONTAINER_DLL_remove (oc_head,
oc_tail,
oc);
oc->suspended = GNUNET_SYSERR;
MHD_resume_connection (oc->connection);
}
}
/**
* Update the phase of @a oc based on @a mret.
*
* @param[in,out] oc order to update phase for
* @param mret #MHD_NO to close with #MHD_NO
* #MHD_YES to close with #MHD_YES
*/
static void
finalize_order (struct OrderContext *oc,
MHD_RESULT mret)
{
oc->phase = (MHD_YES == mret)
? ORDER_PHASE_FINISHED_MHD_YES
: ORDER_PHASE_FINISHED_MHD_NO;
}
/**
* Update the phase of @a oc based on @a ret.
*
* @param[in,out] oc order to update phase for
* @param ret #GNUNET_SYSERR to close with #MHD_NO
* #GNUNET_NO to close with #MHD_YES
* #GNUNET_OK is not allowed!
*/
static void
finalize_order2 (struct OrderContext *oc,
enum GNUNET_GenericReturnValue ret)
{
GNUNET_assert (GNUNET_OK != ret);
oc->phase = (GNUNET_NO == ret)
? ORDER_PHASE_FINISHED_MHD_YES
: ORDER_PHASE_FINISHED_MHD_NO;
}
/**
* Generate an error response for @a oc.
*
* @param[in,out] oc order context to respond to
* @param http_status HTTP status code to set
* @param ec error code to set
* @param detail error message detail to set
*/
static void
reply_with_error (struct OrderContext *oc,
unsigned int http_status,
enum TALER_ErrorCode ec,
const char *detail)
{
MHD_RESULT mret;
mret = TALER_MHD_reply_with_error (oc->connection,
http_status,
ec,
detail);
finalize_order (oc,
mret);
}
/**
* Clean up memory used by @a cls.
*
* @param[in] cls the `struct OrderContext` to clean up
*/
static void
clean_order (void *cls)
{
struct OrderContext *oc = cls;
struct RekeyExchange *rx;
while (NULL != (rx = oc->set_exchanges.pending_reload_head))
{
GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
oc->set_exchanges.pending_reload_tail,
rx);
TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
GNUNET_free (rx->url);
GNUNET_free (rx);
}
if (NULL != oc->set_exchanges.wakeup_task)
{
GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
oc->set_exchanges.wakeup_task = NULL;
}
if (NULL != oc->set_exchanges.exchanges)
{
json_decref (oc->set_exchanges.exchanges);
oc->set_exchanges.exchanges = NULL;
}
if (NULL != oc->parse_order.fulfillment_message_i18n)
{
json_decref (oc->parse_order.fulfillment_message_i18n);
oc->parse_order.fulfillment_message_i18n = NULL;
}
if (NULL != oc->parse_order.summary_i18n)
{
json_decref (oc->parse_order.summary_i18n);
oc->parse_order.summary_i18n = NULL;
}
if (NULL != oc->parse_order.delivery_location)
{
json_decref (oc->parse_order.delivery_location);
oc->parse_order.delivery_location = NULL;
}
if (NULL != oc->merge_inventory.products)
{
json_decref (oc->merge_inventory.products);
oc->merge_inventory.products = NULL;
}
for (unsigned int i = 0; iparse_choices.choices_len; i++)
{
GNUNET_array_grow (oc->parse_choices.choices[i].inputs,
oc->parse_choices.choices[i].inputs_len,
0);
GNUNET_array_grow (oc->parse_choices.choices[i].outputs,
oc->parse_choices.choices[i].outputs_len,
0);
}
GNUNET_array_grow (oc->parse_choices.choices,
oc->parse_choices.choices_len,
0);
for (unsigned int i = 0; iparse_choices.token_families_len; i++)
{
GNUNET_free (oc->parse_choices.token_families[i].name);
GNUNET_free (oc->parse_choices.token_families[i].description);
json_decref (oc->parse_choices.token_families[i].description_i18n);
for (unsigned int j = 0; jparse_choices.token_families[i].keys_len; j++
)
{
GNUNET_CRYPTO_blind_sign_pub_decref (oc->parse_choices.token_families[i].
keys[j].pub.public_key);
}
GNUNET_array_grow (oc->parse_choices.token_families[i].keys,
oc->parse_choices.token_families[i].keys_len,
0);
}
GNUNET_array_grow (oc->parse_choices.token_families,
oc->parse_choices.token_families_len,
0);
GNUNET_array_grow (oc->parse_request.inventory_products,
oc->parse_request.inventory_products_length,
0);
GNUNET_array_grow (oc->parse_request.uuids,
oc->parse_request.uuids_length,
0);
json_decref (oc->parse_request.order);
json_decref (oc->serialize_order.contract);
GNUNET_free (oc->parse_order.merchant_base_url);
GNUNET_free (oc);
}
/**
* Execute the database transaction to setup the order.
*
* @param[in,out] oc order context
* @return transaction status, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a uuids were insufficient to reserve required inventory
*/
static enum GNUNET_DB_QueryStatus
execute_transaction (struct OrderContext *oc)
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp timestamp;
uint64_t order_serial;
if (GNUNET_OK !=
TMH_db->start (TMH_db->cls,
"insert_order"))
{
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
/* Test if we already have an order with this id */
{
json_t *contract_terms;
struct TALER_MerchantPostDataHashP orig_post;
qs = TMH_db->lookup_order (TMH_db->cls,
oc->hc->instance->settings.id,
oc->parse_order.order_id,
&oc->execute_order.token,
&orig_post,
&contract_terms);
/* If yes, check for idempotency */
if (0 > qs)
{
GNUNET_break (0);
TMH_db->rollback (TMH_db->cls);
return qs;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
TMH_db->rollback (TMH_db->cls);
json_decref (contract_terms);
/* Comparing the contract terms is sufficient because all the other
params get added to it at some point. */
if (0 == GNUNET_memcmp (&orig_post,
&oc->parse_request.h_post_data))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order creation idempotent\n");
oc->execute_order.idempotent = true;
return qs;
}
GNUNET_break_op (0);
oc->execute_order.conflict = true;
return qs;
}
}
/* Setup order */
qs = TMH_db->insert_order (TMH_db->cls,
oc->hc->instance->settings.id,
oc->parse_order.order_id,
oc->parse_request.session_id,
&oc->parse_request.h_post_data,
oc->parse_order.pay_deadline,
&oc->parse_request.claim_token,
oc->serialize_order.contract, /* called 'contract terms' at database. */
oc->parse_request.pos_key,
oc->parse_request.pos_algorithm);
if (qs <= 0)
{
/* qs == 0: probably instance does not exist (anymore) */
TMH_db->rollback (TMH_db->cls);
return qs;
}
/* Migrate locks from UUIDs to new order: first release old locks */
for (unsigned int i = 0; iparse_request.uuids_length; i++)
{
qs = TMH_db->unlock_inventory (TMH_db->cls,
&oc->parse_request.uuids[i]);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
return qs;
}
/* qs == 0 is OK here, that just means we did not HAVE any lock under this
UUID */
}
/* Migrate locks from UUIDs to new order: acquire new locks
(note: this can basically ONLY fail on serializability OR
because the UUID locks were insufficient for the desired
quantities). */
for (unsigned int i = 0; iparse_request.inventory_products_length; i++)
{
qs = TMH_db->insert_order_lock (
TMH_db->cls,
oc->hc->instance->settings.id,
oc->parse_order.order_id,
oc->parse_request.inventory_products[i].product_id,
oc->parse_request.inventory_products[i].quantity);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
return qs;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
/* qs == 0: lock acquisition failed due to insufficient stocks */
TMH_db->rollback (TMH_db->cls);
oc->execute_order.out_of_stock_index = i; /* indicate which product is causing the issue */
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
}
oc->execute_order.out_of_stock_index = UINT_MAX;
/* Get the order serial and timestamp for the order we just created to
update long-poll clients. */
qs = TMH_db->lookup_order_summary (TMH_db->cls,
oc->hc->instance->settings.id,
oc->parse_order.order_id,
×tamp,
&order_serial);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
TMH_db->rollback (TMH_db->cls);
return qs;
}
{
enum GNUNET_DB_QueryStatus qs;
json_t *jhook;
jhook = GNUNET_JSON_PACK(
GNUNET_JSON_pack_string("order_id",
oc->parse_order.order_id),
GNUNET_JSON_pack_object_incref("contract",
oc->serialize_order.contract),
GNUNET_JSON_pack_string("instance_id",
oc->hc->instance->settings.id)
);
GNUNET_assert(NULL != jhook);
qs = TMH_trigger_webhook(oc->hc->instance->settings.id,
"order_created",
jhook);
json_decref(jhook);
if (0 >= qs)
{
TMH_db->rollback (TMH_db->cls);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
return qs;
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
reply_with_error (oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_STORE_FAILED,
"failed to trigger webhooks");
return qs;
}
}
TMH_notify_order_change (oc->hc->instance,
TMH_OSF_NONE,
timestamp,
order_serial);
/* finally, commit transaction (note: if it fails, we ALSO re-acquire
the UUID locks, which is exactly what we want) */
qs = TMH_db->commit (TMH_db->cls);
if (0 > qs)
return qs;
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* 1 == success! */
}
/**
* Transform an order into a proposal and store it in the
* database. Write the resulting proposal or an error message
* of a MHD connection.
*
* @param[in,out] oc order context
*/
static void
execute_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
enum GNUNET_DB_QueryStatus qs;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Executing database transaction to create order '%s' for instance '%s'\n",
oc->parse_order.order_id,
settings->id);
for (unsigned int i = 0; ipreflight (TMH_db->cls);
qs = execute_transaction (oc);
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
break;
}
if (0 >= qs)
{
/* Special report if retries insufficient */
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
GNUNET_break (0);
reply_with_error (oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
return;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
/* should be: contract (!) with same order ID
already exists */
reply_with_error (
oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
oc->parse_order.order_id);
return;
}
/* Other hard transaction error (disk full, etc.) */
GNUNET_break (0);
reply_with_error (
oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
return;
}
/* DB transaction succeeded, check for idempotent */
if (oc->execute_order.idempotent)
{
MHD_RESULT ret;
ret = TALER_MHD_REPLY_JSON_PACK (
oc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_string ("order_id",
oc->parse_order.order_id),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_varsize (
"token",
GNUNET_is_zero (&oc->execute_order.token)
? NULL
: &oc->execute_order.token,
sizeof (oc->execute_order.token))));
finalize_order (oc,
ret);
return;
}
if (oc->execute_order.conflict)
{
reply_with_error (
oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
oc->parse_order.order_id);
return;
}
/* DB transaction succeeded, check for out-of-stock */
if (oc->execute_order.out_of_stock_index < UINT_MAX)
{
/* We had a product that has insufficient quantities,
generate the details for the response. */
struct TALER_MERCHANTDB_ProductDetails pd;
MHD_RESULT ret;
const struct InventoryProduct *ip;
size_t num_categories = 0;
uint64_t *categories = NULL;
ip = &oc->parse_request.inventory_products[
oc->execute_order.out_of_stock_index];
memset (&pd,
0,
sizeof (pd));
qs = TMH_db->lookup_product (
TMH_db->cls,
oc->hc->instance->settings.id,
ip->product_id,
&pd,
&num_categories,
&categories);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
GNUNET_free (categories);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order creation failed: product out of stock\n");
ret = TALER_MHD_REPLY_JSON_PACK (
oc->connection,
MHD_HTTP_GONE,
GNUNET_JSON_pack_string (
"product_id",
ip->product_id),
GNUNET_JSON_pack_uint64 (
"requested_quantity",
ip->quantity),
GNUNET_JSON_pack_uint64 (
"available_quantity",
pd.total_stock - pd.total_sold - pd.total_lost),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp (
"restock_expected",
pd.next_restock)));
TALER_MERCHANTDB_product_details_free (&pd);
finalize_order (oc,
ret);
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order creation failed: unknown product out of stock\n");
finalize_order (oc,
TALER_MHD_REPLY_JSON_PACK (
oc->connection,
MHD_HTTP_GONE,
GNUNET_JSON_pack_string (
"product_id",
ip->product_id),
GNUNET_JSON_pack_uint64 (
"requested_quantity",
ip->quantity),
GNUNET_JSON_pack_uint64 (
"available_quantity",
0)));
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
reply_with_error (
oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
return;
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
reply_with_error (
oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
return;
}
GNUNET_break (0);
oc->phase = ORDER_PHASE_FINISHED_MHD_NO;
return;
} /* end 'out of stock' case */
/* Everything in-stock, generate positive response */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order creation succeeded\n");
{
MHD_RESULT ret;
ret = TALER_MHD_REPLY_JSON_PACK (
oc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_string ("order_id",
oc->parse_order.order_id),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_varsize (
"token",
GNUNET_is_zero (&oc->parse_request.claim_token)
? NULL
: &oc->parse_request.claim_token,
sizeof (oc->parse_request.claim_token))));
finalize_order (oc,
ret);
}
}
/**
* Check that the contract is now well-formed. Upon success, continue
* processing with execute_order().
*
* @param[in,out] oc order context
*/
static void
check_contract (struct OrderContext *oc)
{
struct TALER_PrivateContractHashP h_control;
json_dumpf (oc->serialize_order.contract,
stderr,
JSON_INDENT (2));
switch (TALER_JSON_contract_hash (oc->serialize_order.contract,
&h_control))
{
case GNUNET_SYSERR:
GNUNET_break (0);
reply_with_error (
oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
"could not compute hash of serialized order");
return;
case GNUNET_NO:
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
"order contained unallowed values");
return;
case GNUNET_OK:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Contract hash is %s\n",
GNUNET_h2s (&h_control.hash));
oc->phase++;
return;
}
GNUNET_assert (0);
}
/**
* Modify the final contract terms adding salts for
* items that are forgettable.
*
* @param[in,out] oc order context
*/
static void
salt_forgettable (struct OrderContext *oc)
{
if (GNUNET_OK !=
TALER_JSON_contract_seed_forgettable (oc->parse_request.order,
oc->serialize_order.contract))
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_JSON_INVALID,
"could not compute hash of order due to bogus forgettable fields");
return;
}
oc->phase++;
}
/**
* Get rounded time interval. @a start is calculated by rounding
* @a ts down to the nearest multiple of @a precision. @a end is
* the next higher multiple of @a precision.
*
* @param precision rounding precision.
* year, month, day, hour, minute are supported.
* @param ts timestamp to round
* @param[out] start start of the interval
* @param[out] end end of the interval
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
static enum GNUNET_GenericReturnValue
get_rounded_time_interval (struct GNUNET_TIME_Relative precision,
struct GNUNET_TIME_Timestamp ts,
struct GNUNET_TIME_Timestamp *start,
struct GNUNET_TIME_Timestamp *end)
{
struct tm*timeinfo;
time_t seconds;
seconds = GNUNET_TIME_timestamp_to_s (ts);
timeinfo = localtime (&seconds);
if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision))
{
timeinfo->tm_mon = 0;
timeinfo->tm_mday = 1;
timeinfo->tm_hour = 0;
timeinfo->tm_min = 0;
timeinfo->tm_sec = 0;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision))
{
timeinfo->tm_mday = 1;
timeinfo->tm_hour = 0;
timeinfo->tm_min = 0;
timeinfo->tm_sec = 0;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision))
{
timeinfo->tm_hour = 0;
timeinfo->tm_min = 0;
timeinfo->tm_sec = 0;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
{
timeinfo->tm_min = 0;
timeinfo->tm_sec = 0;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
{
timeinfo->tm_sec = 0;
}
else
{
return GNUNET_SYSERR;
}
*start = GNUNET_TIME_timestamp_from_s (mktime (timeinfo));
if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision))
{
timeinfo->tm_year++;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision))
{
timeinfo->tm_mon = (timeinfo->tm_mon + 1) % 12;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision))
{
timeinfo->tm_mday++;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
{
timeinfo->tm_hour++;
}
else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
{
timeinfo->tm_min++;
}
else
{
return GNUNET_SYSERR;
}
*end = GNUNET_TIME_timestamp_from_s (mktime (timeinfo));
return GNUNET_OK;
}
/**
* Check if the token family with the given @a slug is already present in
* the list of token families for this order. If not, fetch its details and
* add it to the list. Then check if there is a public key with a matching
* @a valid_after field. If not, generate a new key pair and store it in the
* database.
*
* @param[in,out] oc order context
* @param slug slug of the token family
* @param[in,out] valid_after validity start date of the token,
subject to rounding. Set to the rounded validity
start date of the matching key.
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
static enum GNUNET_GenericReturnValue
set_token_family (struct OrderContext *oc,
const char *slug,
struct GNUNET_TIME_Timestamp *valid_after)
{
struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
struct TALER_MerchantContractTokenFamily *family = NULL;
enum GNUNET_DB_QueryStatus qs;
/* TODO: Implement rounding duration of token family and use this here. */
struct GNUNET_TIME_Relative precision = GNUNET_TIME_UNIT_MONTHS;
struct GNUNET_TIME_Timestamp min_valid_after;
struct GNUNET_TIME_Timestamp max_valid_after;
if (GNUNET_OK != get_rounded_time_interval (precision,
*valid_after,
&min_valid_after,
&max_valid_after))
{
GNUNET_break (0);
reply_with_error (oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"valid_after");
return GNUNET_SYSERR;
}
for (unsigned int i = 0; iparse_choices.token_families_len; i++)
{
if (0 == strcmp (oc->parse_choices.token_families[i].slug,
slug))
{
family = &oc->parse_choices.token_families[i];
break;
}
}
if (NULL != family)
{
for (unsigned int i = 0; ikeys_len; i++)
{
if (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
>=,
min_valid_after)
&& GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
<,
max_valid_after))
{
/* The token family and a matching key is already added. */
*valid_after = family->keys[i].valid_after;
return GNUNET_OK;
}
}
}
qs = TMH_db->lookup_token_family_key (TMH_db->cls,
oc->hc->instance->settings.id,
slug,
min_valid_after,
max_valid_after,
&key_details);
if (qs <= 0)
{
enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
unsigned int http_status = 0;
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
break;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Token family slug unknown\n");
http_status = MHD_HTTP_NOT_FOUND;
ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* case listed to make compilers happy */
GNUNET_assert (0);
}
GNUNET_break (0);
reply_with_error (oc,
http_status,
ec,
"token_family_slug");
return GNUNET_SYSERR;
}
{
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
/* Verify that the token family is valid right now. */
if (GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_after, >, now)
||
GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_before, <=,
now)
)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Token family expired or not yet valid\n");
reply_with_error (oc,
/* TODO: HTTP Status Code GONE would be more elegant,
but that is already used to indicate that a product is out of stock. */
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_NOT_VALID,
key_details.token_family.slug);
return GNUNET_SYSERR;
}
}
/* slug is not needed */
GNUNET_free (key_details.token_family.slug);
{
struct TALER_MerchantContractTokenFamilyKey key;
if (NULL == family)
{
struct TALER_MerchantContractTokenFamily new_family = {
.slug = slug,
.name = key_details.token_family.name,
.description = key_details.token_family.description,
.description_i18n = key_details.token_family.description_i18n,
.keys = GNUNET_new (struct TALER_MerchantContractTokenFamilyKey),
.keys_len = 0,
};
switch (key_details.token_family.kind)
{
case TALER_MERCHANTDB_TFK_Subscription:
new_family.kind = TALER_MCTK_SUBSCRIPTION;
new_family.critical = true;
// TODO: Set trusted domains
break;
case TALER_MERCHANTDB_TFK_Discount:
new_family.kind = TALER_MCTK_DISCOUNT;
new_family.critical = false;
// TODO: Set expected domains
break;
}
GNUNET_array_append (oc->parse_choices.token_families,
oc->parse_choices.token_families_len,
new_family);
family = &oc->parse_choices.token_families[oc->parse_choices.
token_families_len - 1];
}
if (NULL == key_details.pub.public_key)
{
/* There is no matching key for this token family yet. */
/* We have to generate a fresh key pair. */
/* If public key is NULL, private key must also be NULL */
enum GNUNET_DB_QueryStatus iqs;
struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
struct GNUNET_TIME_Timestamp valid_before =
GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_absolute_add (min_valid_after.abs_time,
key_details.token_family.duration));
GNUNET_assert (NULL == key_details.priv.private_key);
if (GNUNET_TIME_timestamp_cmp (min_valid_after,
<,
key_details.token_family.valid_after))
{
/* If key would start before validity of token family,
set valid_after of key to the value of the token family. */
min_valid_after = key_details.token_family.valid_after;
}
if (GNUNET_TIME_timestamp_cmp (valid_before,
>,
key_details.token_family.valid_before))
{
/* If key would end after validity of token family,
set valid_before of key to the value of the token family. */
valid_before = key_details.token_family.valid_before;
}
GNUNET_CRYPTO_blind_sign_keys_create (&priv,
&pub,
/* TODO: Make cipher and key length configurable */
GNUNET_CRYPTO_BSA_RSA,
4096);
{
struct TALER_TokenIssuePublicKeyP token_pub = {
.public_key = pub,
};
struct TALER_TokenIssuePrivateKeyP token_priv = {
.private_key = priv,
};
iqs = TMH_db->insert_token_family_key (TMH_db->cls,
slug,
&token_pub,
&token_priv,
min_valid_after,
valid_before);
GNUNET_CRYPTO_blind_sign_priv_decref (priv);
if (iqs <= 0)
{
enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
unsigned int http_status = 0;
switch (iqs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
ec = TALER_EC_GENERIC_DB_STORE_FAILED;
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* case listed to make compilers happy */
GNUNET_assert (0);
}
GNUNET_break (0);
reply_with_error (oc,
http_status,
ec,
"token_family_slug");
return GNUNET_SYSERR;
}
key.pub = token_pub;
key.valid_after = min_valid_after;
key.valid_before = valid_before;
}
}
else
{
key.pub = key_details.pub;
key.valid_after = key_details.valid_after;
key.valid_before = key_details.valid_before;
}
GNUNET_array_append (family->keys,
family->keys_len,
key);
*valid_after = key.valid_after;
}
return GNUNET_OK;
}
/**
* Serialize order into @a oc->serialize_order.contract,
* ready to be stored in the database. Upon success, continue
* processing with check_contract().
*
* @param[in,out] oc order context
*/
static void
serialize_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
json_t *merchant;
json_t *token_families = json_object ();
json_t *choices = json_array ();
merchant = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("name",
settings->name),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
settings->website)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("email",
settings->email)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("logo",
settings->logo)));
GNUNET_assert (NULL != merchant);
{
json_t *loca;
/* Handle merchant address */
loca = settings->address;
if (NULL != loca)
{
loca = json_deep_copy (loca);
GNUNET_assert (NULL != loca);
GNUNET_assert (0 ==
json_object_set_new (merchant,
"address",
loca));
}
}
{
json_t *juri;
/* Handle merchant jurisdiction */
juri = settings->jurisdiction;
if (NULL != juri)
{
juri = json_deep_copy (juri);
GNUNET_assert (NULL != juri);
GNUNET_assert (0 ==
json_object_set_new (merchant,
"jurisdiction",
juri));
}
}
for (unsigned int i = 0; iparse_choices.token_families_len; i++)
{
json_t *keys = json_array ();
struct TALER_MerchantContractTokenFamily *family
= &oc->parse_choices.token_families[i];
json_t *jfamily;
for (unsigned int j = 0; jkeys_len; j++)
{
struct TALER_MerchantContractTokenFamilyKey key = family->keys[j];
json_t *jkey = GNUNET_JSON_PACK (
/* TODO: Remove h_pub. */
GNUNET_JSON_pack_data_auto ("h_pub",
&key.pub.public_key->pub_key_hash),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_rsa_public_key ("rsa_pub",
key.pub.public_key->details.
rsa_public_key)),
// GNUNET_JSON_pack_allow_null(
// GNUNET_JSON_pack_data_auto ("cs_pub",
// &key.pub.public_key->details.cs_public_key)),
GNUNET_JSON_pack_int64 ("cipher",
key.pub.public_key->cipher),
GNUNET_JSON_pack_timestamp ("valid_after",
key.valid_after),
GNUNET_JSON_pack_timestamp ("valid_before",
key.valid_before)
);
GNUNET_assert (0 ==
json_array_append_new (keys,
jkey));
}
/* TODO: Add 'details' field. */
jfamily = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("name",
family->name),
GNUNET_JSON_pack_string ("description",
family->description),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("description_i18n",
family->description_i18n)),
GNUNET_JSON_pack_array_steal ("keys",
keys),
GNUNET_JSON_pack_bool ("critical",
family->critical)
);
GNUNET_assert (0 == json_object_set_new (token_families,
family->slug,
jfamily));
}
for (unsigned int i = 0; iparse_choices.choices_len; i++)
{
const struct TALER_MerchantContractChoice *choice
= &oc->parse_choices.choices[i];
json_t *inputs = json_array ();
json_t *outputs = json_array ();
GNUNET_assert (NULL != inputs);
GNUNET_assert (NULL != outputs);
for (unsigned int j = 0; jinputs_len; j++)
{
const struct TALER_MerchantContractInput *input
= &choice->inputs[j];
json_t *jinput;
/* For now, only tokens are supported */
GNUNET_assert (TALER_MCIT_TOKEN == input->type);
jinput = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("kind",
TMH_string_from_contract_input_type (input->
type)),
GNUNET_JSON_pack_string ("token_family_slug",
input->details.token.token_family_slug),
GNUNET_JSON_pack_int64 ("number",
input->details.token.count),
GNUNET_JSON_pack_timestamp ("valid_after",
input->details.token.valid_after)
);
GNUNET_assert (0 ==
json_array_append_new (inputs,
jinput));
}
for (unsigned int j = 0; joutputs_len; j++)
{
struct TALER_MerchantContractOutput *output = &choice->outputs[j];
json_t *joutput;
/* For now, only tokens are supported */
GNUNET_assert (TALER_MCOT_TOKEN == output->type);
joutput = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("kind",
TMH_string_from_contract_output_type (output->
type)),
GNUNET_JSON_pack_string ("token_family_slug",
output->details.token.token_family_slug),
GNUNET_JSON_pack_int64 ("number",
output->details.token.count),
GNUNET_JSON_pack_timestamp ("valid_after",
output->details.token.valid_after)
);
GNUNET_assert (0 ==
json_array_append_new (outputs,
joutput));
}
{
json_t *jchoice
= GNUNET_JSON_PACK (
GNUNET_JSON_pack_array_incref ("inputs",
inputs),
GNUNET_JSON_pack_array_incref ("outputs",
outputs)
);
GNUNET_assert (0 ==
json_array_append_new (choices,
jchoice));
}
}
oc->serialize_order.contract = GNUNET_JSON_PACK (
GNUNET_JSON_pack_int64 ("version",
oc->parse_order.version),
GNUNET_JSON_pack_string ("summary",
oc->parse_order.summary),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("summary_i18n",
oc->parse_order.summary_i18n)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("public_reorder_url",
oc->parse_order.public_reorder_url)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_message",
oc->parse_order.fulfillment_message)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n",
oc->parse_order.fulfillment_message_i18n))
,
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_url",
oc->parse_order.fulfillment_url)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_uint64 ("minimum_age",
oc->parse_order.minimum_age)),
GNUNET_JSON_pack_array_incref ("products",
oc->merge_inventory.products),
GNUNET_JSON_pack_data_auto ("h_wire",
&oc->add_payment_details.wm->h_wire),
GNUNET_JSON_pack_string ("wire_method",
oc->add_payment_details.wm->wire_method),
GNUNET_JSON_pack_string ("order_id",
oc->parse_order.order_id),
GNUNET_JSON_pack_timestamp ("timestamp",
oc->parse_order.timestamp),
GNUNET_JSON_pack_timestamp ("pay_deadline",
oc->parse_order.pay_deadline),
GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
oc->parse_order.wire_deadline),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp ("delivery_date",
oc->parse_order.delivery_date)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("delivery_location",
oc->parse_order.delivery_location)),
GNUNET_JSON_pack_string ("merchant_base_url",
oc->parse_order.merchant_base_url),
GNUNET_JSON_pack_object_steal ("merchant",
merchant),
GNUNET_JSON_pack_data_auto ("merchant_pub",
&oc->hc->instance->merchant_pub),
GNUNET_JSON_pack_array_incref ("exchanges",
oc->set_exchanges.exchanges),
TALER_JSON_pack_amount ("max_fee",
&oc->set_max_fee.max_fee),
TALER_JSON_pack_amount ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_array_steal ("choices",
choices)
),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_steal ("token_families",
token_families)
),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",
(json_t *) oc->parse_order.extra))
);
/* Pack does not work here, because it doesn't set zero-values for timestamps */
GNUNET_assert (0 ==
json_object_set_new (oc->serialize_order.contract,
"refund_deadline",
GNUNET_JSON_from_timestamp (
oc->parse_order.refund_deadline)));
GNUNET_log (
GNUNET_ERROR_TYPE_INFO,
"Refund deadline for contact is %llu\n",
(unsigned long long) oc->parse_order.refund_deadline.abs_time.abs_value_us);
GNUNET_log (
GNUNET_ERROR_TYPE_INFO,
"Wallet timestamp for contact is %llu\n",
(unsigned long long) oc->parse_order.timestamp.abs_time.abs_value_us);
/* Pack does not work here, because it sets zero-values for relative times */
/* auto_refund should only be set if it is not 0 */
if (! GNUNET_TIME_relative_is_zero (oc->parse_order.auto_refund))
{
GNUNET_assert (0 ==
json_object_set_new (oc->serialize_order.contract,
"auto_refund",
GNUNET_JSON_from_time_rel (
oc->parse_order.auto_refund)));
}
oc->phase++;
}
/**
* Set max_fee in @a oc based on STEFAN value if
* not yet present. Upon success, continue
* processing with serialize_order().
*
* @param[in,out] oc order context
*/
static void
set_max_fee (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
if (GNUNET_OK !=
TALER_amount_is_valid (&oc->parse_order.max_fee))
{
struct TALER_Amount stefan;
if ( (settings->use_stefan) &&
(GNUNET_OK ==
TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) )
stefan = oc->set_exchanges.max_stefan_fee;
else
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (oc->parse_order.brutto.currency,
&stefan));
oc->set_max_fee.max_fee = stefan;
}
else
{
oc->set_max_fee.max_fee = oc->parse_order.max_fee;
}
oc->phase++;
}
/**
* Exchange `/keys` processing is done, resume handling
* the order.
*
* @param[in,out] oc context to resume
*/
static void
resume_with_keys (struct OrderContext *oc)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Resuming order processing after /keys downloads (now have %u accounts)\n",
(unsigned int) json_array_size (oc->set_exchanges.exchanges));
GNUNET_assert (GNUNET_YES == oc->suspended);
GNUNET_CONTAINER_DLL_remove (oc_head,
oc_tail,
oc);
oc->suspended = GNUNET_NO;
MHD_resume_connection (oc->connection);
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
/**
* Update MAX STEFAN fees based on @a keys.
*
* @param[in,out] oc order context to update
* @param keys keys to derive STEFAN fees from
*/
static void
update_stefan (struct OrderContext *oc,
const struct TALER_EXCHANGE_Keys *keys)
{
struct TALER_Amount net;
if (GNUNET_SYSERR !=
TALER_EXCHANGE_keys_stefan_b2n (keys,
&oc->parse_order.brutto,
&net))
{
struct TALER_Amount fee;
TALER_EXCHANGE_keys_stefan_round (keys,
&net);
if (-1 == TALER_amount_cmp (&oc->parse_order.brutto,
&net))
{
/* brutto < netto! */
/* => after rounding, there is no real difference */
net = oc->parse_order.brutto;
}
GNUNET_assert (0 <=
TALER_amount_subtract (&fee,
&oc->parse_order.brutto,
&net));
if ( (GNUNET_OK !=
TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) ||
(-1 == TALER_amount_cmp (&oc->set_exchanges.max_stefan_fee,
&fee)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Updated STEFAN-based fee to %s\n",
TALER_amount2s (&fee));
oc->set_exchanges.max_stefan_fee = fee;
}
}
}
/**
* Compute the set of exchanges that would be acceptable
* for this order.
*
* @param cls our `struct OrderContext`
* @param url base URL of an exchange (not used)
* @param exchange internal handle for the exchange
*/
static void
get_acceptable (void *cls,
const char *url,
const struct TMH_Exchange *exchange)
{
struct OrderContext *oc = cls;
unsigned int priority = 42; /* make compiler happy */
json_t *j_exchange;
enum GNUNET_GenericReturnValue res;
struct TALER_Amount max_amount;
max_amount = oc->parse_order.brutto;
res = TMH_exchange_check_debit (
oc->hc->instance->settings.id,
exchange,
oc->add_payment_details.wm,
&max_amount);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange %s evaluated at %d with max %s\n",
url,
res,
TALER_amount2s (&max_amount));
if ( (! TALER_amount_is_zero (&max_amount)) &&
(TALER_amount_is_zero (&max_amount)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange %s deposit limit is zero, skipping it\n",
url);
return;
}
switch (res)
{
case GNUNET_OK:
priority = 1024; /* high */
oc->set_exchanges.exchange_good = true;
break;
case GNUNET_NO:
if (oc->set_exchanges.forced_reload)
priority = 0; /* fresh negative response */
else
priority = 512; /* stale negative response */
break;
case GNUNET_SYSERR:
if (oc->set_exchanges.forced_reload)
priority = 256; /* fresh, no accounts yet */
else
priority = 768; /* stale, no accounts yet */
break;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Exchange %s deposit limit is %s, adding it!\n",
url,
TALER_amount2s (&max_amount));
GNUNET_assert (0 <=
TALER_amount_add (
&oc->set_exchanges.total_exchange_limit,
&oc->set_exchanges.total_exchange_limit,
&max_amount));
GNUNET_assert (GNUNET_OK ==
TALER_amount_min (&oc->set_exchanges.total_exchange_limit,
&oc->set_exchanges.total_exchange_limit,
&oc->parse_order.brutto));
j_exchange = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("url",
url),
GNUNET_JSON_pack_uint64 ("priority",
priority),
TALER_JSON_pack_amount ("max_contribution",
&max_amount),
GNUNET_JSON_pack_data_auto ("master_pub",
TMH_EXCHANGES_get_master_pub (exchange)));
GNUNET_assert (NULL != j_exchange);
GNUNET_assert (0 ==
json_array_append_new (oc->set_exchanges.exchanges,
j_exchange));
}
/**
* Function called with the result of a #TMH_EXCHANGES_keys4exchange()
* operation.
*
* @param cls closure with our `struct RekeyExchange *`
* @param keys the keys of the exchange
* @param exchange representation of the exchange
*/
static void
keys_cb (
void *cls,
struct TALER_EXCHANGE_Keys *keys,
struct TMH_Exchange *exchange)
{
struct RekeyExchange *rx = cls;
struct OrderContext *oc = rx->oc;
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
rx->fo = NULL;
GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
oc->set_exchanges.pending_reload_tail,
rx);
if (NULL == keys)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to download %skeys\n",
rx->url);
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Got response for %skeys\n",
rx->url);
if ( (settings->use_stefan) &&
(GNUNET_OK !=
TALER_amount_is_valid (&oc->parse_order.max_fee)) )
update_stefan (oc,
keys);
get_acceptable (oc,
rx->url,
exchange);
}
GNUNET_free (rx->url);
GNUNET_free (rx);
if (NULL != oc->set_exchanges.pending_reload_head)
return;
resume_with_keys (oc);
}
/**
* Force re-downloading of /keys from @a exchange,
* we currently have no acceptable exchange, so we
* should try to get one.
*
* @param cls closure with our `struct OrderContext`
* @param url base URL of the exchange
* @param exchange internal handle for the exchange
*/
static void
get_exchange_keys (void *cls,
const char *url,
const struct TMH_Exchange *exchange)
{
struct OrderContext *oc = cls;
struct RekeyExchange *rx;
rx = GNUNET_new (struct RekeyExchange);
rx->oc = oc;
rx->url = GNUNET_strdup (url);
GNUNET_CONTAINER_DLL_insert (oc->set_exchanges.pending_reload_head,
oc->set_exchanges.pending_reload_tail,
rx);
if (oc->set_exchanges.forced_reload)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Forcing download of %skeys\n",
url);
rx->fo = TMH_EXCHANGES_keys4exchange (url,
oc->set_exchanges.forced_reload,
&keys_cb,
rx);
}
/**
* Check our KYC status at all exchanges as our current limit is
* too low and we failed to create an order.
*
* @param oc order context
*/
static void
notify_kyc_required (const struct OrderContext *oc)
{
struct GNUNET_DB_EventHeaderP es = {
.size = htons (sizeof (es)),
.type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_RULE_TRIGGERED)
};
char *hws;
char *extra;
json_t *exchange;
size_t i;
hws = GNUNET_STRINGS_data_to_string_alloc (
&oc->add_payment_details.wm->h_wire,
sizeof (oc->add_payment_details.wm->h_wire));
json_array_foreach (oc->set_exchanges.exchanges, i, exchange)
{
const char *exchange_url
= json_string_value (json_object_get (exchange,
"url"));
GNUNET_asprintf (&extra,
"%s %s",
hws,
exchange_url);
TMH_db->event_notify (TMH_db->cls,
&es,
extra,
strlen (extra) + 1);
GNUNET_free (extra);
}
GNUNET_free (hws);
}
/**
* Task run when we are timing out on /keys and will just
* proceed with what we got.
*
* @param cls our `struct OrderContext *` to resume
*/
static void
wakeup_timeout (void *cls)
{
struct OrderContext *oc = cls;
oc->set_exchanges.wakeup_task = NULL;
GNUNET_assert (GNUNET_YES == oc->suspended);
GNUNET_CONTAINER_DLL_remove (oc_head,
oc_tail,
oc);
MHD_resume_connection (oc->connection);
oc->suspended = GNUNET_NO;
TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}
/**
* Set list of acceptable exchanges in @a oc. Upon success, continue
* processing with set_max_fee().
*
* @param[in,out] oc order context
* @return true to suspend execution
*/
static bool
set_exchanges (struct OrderContext *oc)
{
if (NULL != oc->set_exchanges.wakeup_task)
{
GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
oc->set_exchanges.wakeup_task = NULL;
}
if (TALER_amount_is_zero (&oc->parse_order.brutto))
{
/* Total amount is zero, so we don't actually need exchanges! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order total is zero, no need for exchanges\n");
oc->set_exchanges.exchanges = json_array ();
GNUNET_assert (NULL != oc->set_exchanges.exchanges);
oc->phase++;
return false;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order total is %s, trying to find exchanges\n",
TALER_amount2s (&oc->parse_order.brutto));
/* Note: re-building 'oc->set_exchanges.exchanges' every time here might be a
tad expensive; could likely consider caching the result if it starts to
matter. */
if (NULL == oc->set_exchanges.exchanges)
{
oc->set_exchanges.keys_timeout
= GNUNET_TIME_relative_to_absolute (MAX_KEYS_WAIT);
oc->set_exchanges.exchanges = json_array ();
GNUNET_assert (NULL != oc->set_exchanges.exchanges);
GNUNET_assert (
GNUNET_OK ==
TALER_amount_set_zero (oc->parse_order.brutto.currency,
&oc->set_exchanges.total_exchange_limit));
TMH_exchange_get_trusted (&get_exchange_keys,
oc);
}
else if (! oc->set_exchanges.exchange_good)
{
if (! oc->set_exchanges.forced_reload)
{
oc->set_exchanges.forced_reload = true;
GNUNET_assert (0 ==
json_array_clear (oc->set_exchanges.exchanges));
TMH_exchange_get_trusted (&get_exchange_keys,
oc);
}
}
if (GNUNET_TIME_absolute_is_past (oc->set_exchanges.keys_timeout))
{
struct RekeyExchange *rx;
while (NULL != (rx = oc->set_exchanges.pending_reload_head))
{
GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
oc->set_exchanges.pending_reload_tail,
rx);
TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
GNUNET_free (rx->url);
GNUNET_free (rx);
}
}
if (NULL != oc->set_exchanges.pending_reload_head)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Still trying to (re)load %skeys\n",
oc->set_exchanges.pending_reload_head->url);
oc->set_exchanges.wakeup_task
= GNUNET_SCHEDULER_add_at (oc->set_exchanges.keys_timeout,
&wakeup_timeout,
oc);
MHD_suspend_connection (oc->connection);
oc->suspended = GNUNET_YES;
GNUNET_CONTAINER_DLL_insert (oc_head,
oc_tail,
oc);
return true; /* reloads pending */
}
if (0 == json_array_size (oc->set_exchanges.exchanges))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Cannot create order: lacking trusted exchanges\n");
reply_with_error (
oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
oc->add_payment_details.wm->wire_method);
return false;
}
if (1 ==
TALER_amount_cmp (&oc->parse_order.brutto,
&oc->set_exchanges.total_exchange_limit))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Cannot create order: %s is the sum of hard limits from supported exchanges\n",
TALER_amount2s (&oc->set_exchanges.total_exchange_limit));
notify_kyc_required (oc);
reply_with_error (
oc,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS,
TALER_amount2s (&oc->set_exchanges.total_exchange_limit));
return false;
}
if (! oc->set_exchanges.exchange_good)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Creating order, but possibly without usable trusted exchanges\n");
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Can create order: %s is the sum of hard limits from supported exchanges\n",
TALER_amount2s (&oc->set_exchanges.total_exchange_limit));
}
oc->phase++;
return false;
}
/**
* Parse the order field of the request. Upon success, continue
* processing with parse_choices().
*
* @param[in,out] oc order context
*/
static void
parse_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
const char *merchant_base_url = NULL;
uint64_t version = 0;
const json_t *jmerchant = NULL;
/* auto_refund only needs to be type-checked,
* mostly because in GNUnet relative times can't
* be negative. */
bool no_fee;
/* TODO: Move "amount" field to choices. This entails moving the
stefan-base fee calculation to the parse_choices phase. */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint64 ("version",
&version),
NULL),
TALER_JSON_spec_amount_any ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_spec_string ("summary",
&oc->parse_order.summary),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("products",
&oc->parse_order.products),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("summary_i18n",
&oc->parse_order.summary_i18n),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("order_id",
&oc->parse_order.order_id),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("fulfillment_message",
&oc->parse_order.fulfillment_message),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("fulfillment_message_i18n",
&oc->parse_order.fulfillment_message_i18n),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("fulfillment_url",
&oc->parse_order.fulfillment_url),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("public_reorder_url",
&oc->parse_order.public_reorder_url),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("choices",
&oc->parse_order.choices),
NULL),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_web_url ("merchant_base_url",
&merchant_base_url),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_object_const ("merchant",
&jmerchant),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("timestamp",
&oc->parse_order.timestamp),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("refund_deadline",
&oc->parse_order.refund_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("pay_deadline",
&oc->parse_order.pay_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
&oc->parse_order.wire_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_amount_any ("max_fee",
&oc->parse_order.max_fee),
&no_fee),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_json ("delivery_location",
&oc->parse_order.delivery_location),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("delivery_date",
&oc->parse_order.delivery_date),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint32 ("minimum_age",
&oc->parse_order.minimum_age),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("auto_refund",
&oc->parse_order.auto_refund),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_object_const ("extra",
&oc->parse_order.extra),
NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
oc->parse_order.wire_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
ret = TALER_MHD_parse_json_data (oc->connection,
oc->parse_request.order,
spec);
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
finalize_order2 (oc,
ret);
return;
}
if (0 == version)
{
oc->parse_order.version = TALER_MCV_V0;
if (NULL != oc->parse_order.choices)
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
"choices array must be null for v0 contracts");
return;
}
}
else if (1 == version)
{
oc->parse_order.version = TALER_MCV_V1;
if (! json_is_array (oc->parse_order.choices))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"order.choices is not a valid array");
return;
}
}
else
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_VERSION_MALFORMED,
"invalid version specified in order, supported are null, '0' or '1'");
return;
}
if (! TMH_test_exchange_configured_for_currency (
oc->parse_order.brutto.currency))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_CURRENCY_MISMATCH,
"no trusted exchange for this currency");
return;
}
if ( (! no_fee) &&
(GNUNET_OK !=
TALER_amount_cmp_currency (&oc->parse_order.brutto,
&oc->parse_order.max_fee)) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_CURRENCY_MISMATCH,
"different currencies used for 'max_fee' and 'amount' currency");
return;
}
/* Add order_id if it doesn't exist. */
if (NULL == oc->parse_order.order_id)
{
char buf[256];
time_t timer;
struct tm *tm_info;
size_t off;
uint64_t rand;
char *last;
time (&timer);
tm_info = localtime (&timer);
if (NULL == tm_info)
{
GNUNET_JSON_parse_free (spec);
reply_with_error (
oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME,
NULL);
return;
}
off = strftime (buf,
sizeof (buf) - 1,
"%Y.%j",
tm_info);
/* Check for error state of strftime */
GNUNET_assert (0 != off);
buf[off++] = '-';
rand = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
UINT64_MAX);
last = GNUNET_STRINGS_data_to_string (&rand,
sizeof (uint64_t),
&buf[off],
sizeof (buf) - off);
GNUNET_assert (NULL != last);
*last = '\0';
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Assigning order ID `%s' server-side\n",
buf);
oc->parse_order.order_id = GNUNET_strdup (buf);
GNUNET_assert (NULL != oc->parse_order.order_id);
}
/* Patch fulfillment URL with order_id (implements #6467). */
if (NULL != oc->parse_order.fulfillment_url)
{
const char *pos;
pos = strstr (oc->parse_order.fulfillment_url,
"${ORDER_ID}");
if (NULL != pos)
{
/* replace ${ORDER_ID} with the real order_id */
char *nurl;
/* We only allow one placeholder */
if (strstr (pos + strlen ("${ORDER_ID}"),
"${ORDER_ID}"))
{
GNUNET_break_op (0);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"fulfillment_url");
return;
}
GNUNET_asprintf (&nurl,
"%.*s%s%s",
/* first output URL until ${ORDER_ID} */
(int) (pos - oc->parse_order.fulfillment_url),
oc->parse_order.fulfillment_url,
/* replace ${ORDER_ID} with the right order_id */
oc->parse_order.order_id,
/* append rest of original URL */
pos + strlen ("${ORDER_ID}"));
oc->parse_order.fulfillment_url = GNUNET_strdup (nurl);
GNUNET_free (nurl);
}
}
/* Check soundness of refund deadline, and that a timestamp
* is actually present. */
{
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
/* Add timestamp if it doesn't exist (or is zero) */
if (GNUNET_TIME_absolute_is_zero (oc->parse_order.timestamp.abs_time))
{
oc->parse_order.timestamp = now;
}
/* If no refund_deadline given, set one based on refund_delay. */
if (GNUNET_TIME_absolute_is_never (
oc->parse_order.refund_deadline.abs_time))
{
if (GNUNET_TIME_relative_is_zero (oc->parse_request.refund_delay))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Refund delay is zero, no refunds are possible for this order\n");
oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
}
else
{
oc->parse_order.refund_deadline = GNUNET_TIME_relative_to_timestamp (
oc->parse_request.refund_delay);
}
}
if ( (! GNUNET_TIME_absolute_is_zero (
oc->parse_order.delivery_date.abs_time)) &&
(GNUNET_TIME_absolute_is_past (
oc->parse_order.delivery_date.abs_time)) )
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST,
NULL);
return;
}
}
if (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time))
{
oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp (
settings->default_pay_delay);
}
else if (GNUNET_TIME_absolute_is_past (oc->parse_order.pay_deadline.abs_time))
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PAY_DEADLINE_IN_PAST,
NULL);
return;
}
if ( (! GNUNET_TIME_absolute_is_zero (
oc->parse_order.refund_deadline.abs_time)) &&
(GNUNET_TIME_absolute_is_past (
oc->parse_order.refund_deadline.abs_time)) )
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_DEADLINE_IN_PAST,
NULL);
return;
}
if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
{
struct GNUNET_TIME_Timestamp t;
t = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_relative_max (settings->default_wire_transfer_delay,
oc->parse_request.refund_delay));
oc->parse_order.wire_deadline = GNUNET_TIME_timestamp_max (
oc->parse_order.refund_deadline,
t);
if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER,
"order:wire_transfer_deadline");
return;
}
}
if (GNUNET_TIME_timestamp_cmp (oc->parse_order.wire_deadline,
<,
oc->parse_order.refund_deadline))
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE,
"order:wire_transfer_deadline;order:refund_deadline");
return;
}
if (NULL != merchant_base_url)
{
if (('\0' == *merchant_base_url) ||
('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
"merchant_base_url is not valid");
return;
}
oc->parse_order.merchant_base_url
= GNUNET_strdup (merchant_base_url);
}
else
{
char *url;
url = make_merchant_base_url (oc->connection,
settings->id);
if (NULL == url)
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MISSING,
"order:merchant_base_url");
return;
}
oc->parse_order.merchant_base_url = url;
}
if ( (NULL != oc->parse_order.products) &&
(! TMH_products_array_valid (oc->parse_order.products)) )
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"order.products");
return;
}
/* Merchant information must not already be present */
if (NULL != jmerchant)
{
GNUNET_break_op (0);
reply_with_error (
oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
"'merchant' field already set, but must be provided by backend");
return;
}
if ( (NULL != oc->parse_order.delivery_location) &&
(! TMH_location_object_valid (oc->parse_order.delivery_location)) )
{
GNUNET_break_op (0);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"delivery_location");
return;
}
oc->phase++;
}
/**
* Parse contract choices. Upon success, continue
* processing with merge_inventory().
*
* @param[in,out] oc order context
*/
static void
parse_choices (struct OrderContext *oc)
{
if (NULL == oc->parse_order.choices)
{
oc->phase++;
return;
}
GNUNET_array_grow (oc->parse_choices.choices,
oc->parse_choices.choices_len,
json_array_size (oc->parse_order.choices));
for (unsigned int i = 0; iparse_choices.choices_len; i++)
{
const char *error_name;
unsigned int error_line;
const json_t *jinputs;
const json_t *joutputs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("inputs",
&jinputs),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("outputs",
&joutputs),
NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i),
spec,
&error_name,
&error_line);
if (GNUNET_OK != ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Choice parsing failed: %s:%u\n",
error_name,
error_line);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"choice");
return;
}
if (0 == json_array_size (jinputs) && 0 == json_array_size (joutputs))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Choice #%u must have at least one input or output\n",
i);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"choice");
return;
}
{
// TODO: Maybe move to a separate function
const json_t *jinput;
size_t idx;
json_array_foreach ((json_t *) jinputs, idx, jinput)
{
struct TALER_MerchantContractInput input = {.details.token.count = 1};
const char *kind;
const char *ierror_name;
unsigned int ierror_line;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("kind",
&kind),
GNUNET_JSON_spec_string ("token_family_slug",
&input.details.token.token_family_slug),
/* TODO: Remove valid_after field for inputs,
use current system time instead. */
GNUNET_JSON_spec_timestamp ("valid_after",
&input.details.token.valid_after),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint32 ("count",
&input.details.token.count),
NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (jinput,
ispec,
&ierror_name,
&ierror_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Invalid input #%u for field %s\n",
(unsigned int) idx,
ierror_name);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
ierror_name);
return;
}
input.type = TMH_contract_input_type_from_string (kind);
if (TALER_MCIT_INVALID == input.type)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Field 'kind' invalid in input #%u\n",
(unsigned int) idx);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"kind");
return;
}
if (0 == input.details.token.count)
{
/* Ignore inputs with 'number' field set to 0 */
continue;
}
if (GNUNET_OK != set_token_family (oc,
input.details.token.token_family_slug
,
&input.details.token.valid_after))
{
/* error is already scheduled, return. */
return;
}
GNUNET_array_append (oc->parse_choices.choices[i].inputs,
oc->parse_choices.choices[i].inputs_len,
input);
}
}
{
const json_t *joutput;
size_t idx;
json_array_foreach ((json_t *) joutputs, idx, joutput)
{
struct TALER_MerchantContractOutput output = { .details.token.count = 1}
;
const char *kind;
const char *ierror_name;
unsigned int ierror_line;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("kind",
&kind),
GNUNET_JSON_spec_string ("token_family_slug",
&output.details.token.token_family_slug),
/* TODO: Make valid_after optional, default to current system time. */
GNUNET_JSON_spec_timestamp ("valid_after",
&output.details.token.valid_after),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint32 ("count",
&output.details.token.count),
NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (joutput,
ispec,
&ierror_name,
&ierror_line))
{
GNUNET_JSON_parse_free (spec);
GNUNET_JSON_parse_free (ispec);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Invalid output #%u for field %s\n",
(unsigned int) idx,
ierror_name);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
ierror_name);
return;
}
output.type = TMH_contract_output_type_from_string (kind);
if (TALER_MCOT_INVALID == output.type)
{
GNUNET_JSON_parse_free (spec);
GNUNET_JSON_parse_free (ispec);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Field 'kind' invalid in output #%u\n",
(unsigned int) idx);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"kind");
return;
}
if (0 == output.details.token.count)
{
/* Ignore outputs with 'number' field set to 0. */
continue;
}
if (GNUNET_OK != set_token_family (oc,
output.details.token.
token_family_slug,
&output.details.token.valid_after))
{
/* Error is already scheduled, return. */
return;
}
GNUNET_array_append (oc->parse_choices.choices[i].outputs,
oc->parse_choices.choices[i].outputs_len,
output);
}
}
}
oc->phase++;
}
/**
* Process the @a payment_target and add the details of how the
* order could be paid to @a order. On success, continue
* processing with set_exchanges().
*
* @param[in,out] oc order context
*/
static void
add_payment_details (struct OrderContext *oc)
{
struct TMH_WireMethod *wm;
wm = oc->hc->instance->wm_head;
/* Locate wire method that has a matching payment target */
while ( (NULL != wm) &&
( (! wm->active) ||
( (NULL != oc->parse_request.payment_target) &&
(0 != strcasecmp (oc->parse_request.payment_target,
wm->wire_method) ) ) ) )
wm = wm->next;
if (NULL == wm)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No wire method available for instance '%s'\n",
oc->hc->instance->settings.id);
reply_with_error (oc,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
oc->parse_request.payment_target);
return;
}
oc->add_payment_details.wm = wm;
oc->phase++;
}
/**
* Merge the inventory products into products, querying the
* database about the details of those products. Upon success,
* continue processing by calling add_payment_details().
*
* @param[in,out] oc order context to process
*/
static void
merge_inventory (struct OrderContext *oc)
{
/**
* parse_request.inventory_products => instructions to add products to contract terms
* parse_order.products => contains products that are not from the backend-managed inventory.
*/
if (NULL != oc->parse_order.products)
oc->merge_inventory.products
= json_deep_copy (oc->parse_order.products);
else
oc->merge_inventory.products
= json_array ();
/* Populate products from inventory product array and database */
{
GNUNET_assert (NULL != oc->merge_inventory.products);
for (unsigned int i = 0; iparse_request.inventory_products_length; i++)
{
const struct InventoryProduct *ip
= &oc->parse_request.inventory_products[i];
struct TALER_MERCHANTDB_ProductDetails pd;
enum GNUNET_DB_QueryStatus qs;
size_t num_categories = 0;
uint64_t *categories = NULL;
qs = TMH_db->lookup_product (TMH_db->cls,
oc->hc->instance->settings.id,
ip->product_id,
&pd,
&num_categories,
&categories);
if (qs <= 0)
{
enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
unsigned int http_status = 0;
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
break;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
break;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product %s from order unknown\n",
ip->product_id);
http_status = MHD_HTTP_NOT_FOUND;
ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* case listed to make compilers happy */
GNUNET_assert (0);
}
reply_with_error (oc,
http_status,
ec,
ip->product_id);
return;
}
GNUNET_free (categories);
oc->parse_order.minimum_age
= GNUNET_MAX (oc->parse_order.minimum_age,
pd.minimum_age);
{
json_t *p;
p = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("description",
pd.description),
GNUNET_JSON_pack_object_steal ("description_i18n",
pd.description_i18n),
GNUNET_JSON_pack_string ("unit",
pd.unit),
TALER_JSON_pack_amount ("price",
&pd.price),
GNUNET_JSON_pack_array_steal ("taxes",
pd.taxes),
GNUNET_JSON_pack_string ("image",
pd.image),
GNUNET_JSON_pack_uint64 (
"quantity",
ip->quantity));
GNUNET_assert (NULL != p);
GNUNET_assert (0 ==
json_array_append_new (oc->merge_inventory.products,
p));
}
GNUNET_free (pd.description);
GNUNET_free (pd.unit);
GNUNET_free (pd.image);
json_decref (pd.address);
}
}
/* check if final product list is well-formed */
if (! TMH_products_array_valid (oc->merge_inventory.products))
{
GNUNET_break_op (0);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"order:products");
return;
}
oc->phase++;
}
/**
* Parse the client request. Upon success,
* continue processing by calling parse_order().
*
* @param[in,out] oc order context to process
*/
static void
parse_request (struct OrderContext *oc)
{
const json_t *ip = NULL;
const json_t *uuid = NULL;
const char *otp_id = NULL;
bool create_token = true; /* default */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("order",
&oc->parse_request.order),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("refund_delay",
&oc->parse_request.refund_delay),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("payment_target",
&oc->parse_request.payment_target),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("inventory_products",
&ip),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("session_id",
&oc->parse_request.session_id),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("lock_uuids",
&uuid),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool ("create_token",
&create_token),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("otp_id",
&otp_id),
NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
ret = TALER_MHD_parse_json_data (oc->connection,
oc->hc->request_body,
spec);
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
finalize_order2 (oc,
ret);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Refund delay is %s\n",
GNUNET_TIME_relative2s (oc->parse_request.refund_delay,
false));
TMH_db->expire_locks (TMH_db->cls);
if (NULL != otp_id)
{
struct TALER_MERCHANTDB_OtpDeviceDetails td;
enum GNUNET_DB_QueryStatus qs;
qs = TMH_db->select_otp (TMH_db->cls,
oc->hc->instance->settings.id,
otp_id,
&td);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
reply_with_error (oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"select_otp");
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
reply_with_error (oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
"select_otp");
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
reply_with_error (oc,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
otp_id);
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
oc->parse_request.pos_key = td.otp_key;
oc->parse_request.pos_algorithm = td.otp_algorithm;
}
if (create_token)
{
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&oc->parse_request.claim_token,
sizeof (oc->parse_request.claim_token));
}
/* Compute h_post_data (for idempotency check) */
{
char *req_body_enc;
/* Dump normalized JSON to string. */
if (NULL == (req_body_enc
= json_dumps (oc->hc->request_body,
JSON_ENCODE_ANY
| JSON_COMPACT
| JSON_SORT_KEYS)))
{
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_ALLOCATION_FAILURE,
"request body normalization for hashing");
return;
}
GNUNET_CRYPTO_hash (req_body_enc,
strlen (req_body_enc),
&oc->parse_request.h_post_data.hash);
GNUNET_free (req_body_enc);
}
/* parse the inventory_products (optionally given) */
if (NULL != ip)
{
unsigned int ipl = (unsigned int) json_array_size (ip);
if ( (json_array_size (ip) != (size_t) ipl) ||
(ipl > MAX_PRODUCTS) )
{
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
reply_with_error (oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_ALLOCATION_FAILURE,
"inventory products too long");
return;
}
GNUNET_array_grow (oc->parse_request.inventory_products,
oc->parse_request.inventory_products_length,
(unsigned int) json_array_size (ip));
for (unsigned int i = 0; iparse_request.inventory_products_length; i++)
{
struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i];
const char *error_name;
unsigned int error_line;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("product_id",
&ipr->product_id),
GNUNET_JSON_spec_uint32 ("quantity",
&ipr->quantity),
GNUNET_JSON_spec_end ()
};
ret = GNUNET_JSON_parse (json_array_get (ip,
i),
ispec,
&error_name,
&error_line);
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product parsing failed at #%u: %s:%u\n",
i,
error_name,
error_line);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"inventory_products");
return;
}
}
}
/* parse the lock_uuids (optionally given) */
if (NULL != uuid)
{
GNUNET_array_grow (oc->parse_request.uuids,
oc->parse_request.uuids_length,
json_array_size (uuid));
for (unsigned int i = 0; iparse_request.uuids_length; i++)
{
json_t *ui = json_array_get (uuid,
i);
if (! json_is_string (ui))
{
GNUNET_break_op (0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"UUID parsing failed at #%u\n",
i);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"lock_uuids");
return;
}
TMH_uuid_from_string (json_string_value (ui),
&oc->parse_request.uuids[i]);
}
}
oc->phase++;
}
MHD_RESULT
TMH_private_post_orders (
const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
struct OrderContext *oc = hc->ctx;
if (NULL == oc)
{
oc = GNUNET_new (struct OrderContext);
hc->ctx = oc;
hc->cc = &clean_order;
oc->connection = connection;
oc->hc = hc;
}
while (1)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Processing order in phase %d\n",
oc->phase);
switch (oc->phase)
{
case ORDER_PHASE_PARSE_REQUEST:
parse_request (oc);
break;
case ORDER_PHASE_PARSE_ORDER:
parse_order (oc);
break;
case ORDER_PHASE_PARSE_CHOICES:
parse_choices (oc);
break;
case ORDER_PHASE_MERGE_INVENTORY:
merge_inventory (oc);
break;
case ORDER_PHASE_ADD_PAYMENT_DETAILS:
add_payment_details (oc);
break;
case ORDER_PHASE_SET_EXCHANGES:
if (set_exchanges (oc))
return MHD_YES;
break;
case ORDER_PHASE_SET_MAX_FEE:
set_max_fee (oc);
break;
case ORDER_PHASE_SERIALIZE_ORDER:
serialize_order (oc);
break;
case ORDER_PHASE_CHECK_CONTRACT:
check_contract (oc);
break;
case ORDER_PHASE_SALT_FORGETTABLE:
salt_forgettable (oc);
break;
case ORDER_PHASE_EXECUTE_ORDER:
execute_order (oc);
break;
case ORDER_PHASE_FINISHED_MHD_YES:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Finished processing order (1)\n");
return MHD_YES;
case ORDER_PHASE_FINISHED_MHD_NO:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Finished processing order (0)\n");
return MHD_NO;
}
}
}
/* end of taler-merchant-httpd_private-post-orders.c */