/* This file is part of TALER (C) 2014-2020 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 backend/taler-merchant-httpd_get-orders-ID.c * @brief implementation of GET /orders/$ID * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_get-orders-ID.h" #include "taler-merchant-httpd_mhd.h" #include "taler-merchant-httpd_qr.h" #include "taler-merchant-httpd_templating.h" /** * How often do we retry DB transactions on serialization failures? */ #define MAX_RETRIES 5 /** * Context for the operation. */ struct GetOrderData { /** * Hashed version of contract terms. All zeros if not provided. */ struct GNUNET_HashCode h_contract_terms; /** * Claim token used for access control. All zeros if not provided. */ struct TALER_ClaimTokenP claim_token; /** * DLL of (suspended) requests. */ struct GetOrderData *next; /** * DLL of (suspended) requests. */ struct GetOrderData *prev; /** * Context of the request. */ struct TMH_HandlerContext *hc; /** * Entry in the #resume_timeout_heap for this check payment, if we are * suspended. */ struct TMH_SuspendedConnection sc; /** * Which merchant instance is this for? */ struct MerchantInstance *mi; /** * order ID for the payment */ const char *order_id; /** * Where to get the contract */ const char *contract_url; /** * fulfillment URL of the contract (valid as long as @e contract_terms is * valid; but can also be NULL if the contract_terms does not come with * a fulfillment URL). */ const char *fulfillment_url; /** * session of the client */ const char *session_id; /** * Contract terms of the payment we are checking. NULL when they * are not (yet) known. */ json_t *contract_terms; /** * Total refunds granted for this payment. Only initialized * if @e refunded is set to true. */ struct TALER_Amount refund_amount; /** * Did we suspend @a connection? */ bool suspended; /** * Return code: #TALER_EC_NONE if successful. */ enum TALER_ErrorCode ec; /** * Set to true if we are dealing with an unclaimed order * (and thus @e h_contract_terms is not set, and certain * DB queries will not work). */ bool unclaimed; /** * Set to true if this payment has been refunded and * @e refund_amount is initialized. */ bool refunded; /** * Set to true if a refund is still available for the * wallet for this payment. */ bool refund_available; /** * Set to true if the client requested HTML, otherwise we generate JSON. */ bool generate_html; }; /** * Head of DLL of (suspended) requests. */ static struct GetOrderData *god_head; /** * Tail of DLL of (suspended) requests. */ static struct GetOrderData *god_tail; /** * Force resuming all suspended order lookups, needed during shutdown. */ void TMH_force_wallet_get_order_resume (void) { struct GetOrderData *god; while (NULL != (god = god_head)) { GNUNET_CONTAINER_DLL_remove (god_head, god_tail, god); GNUNET_assert (god->suspended); god->suspended = false; MHD_resume_connection (god->sc.con); } } /** * Suspend this @a god until the trigger is satisfied. * * @param god request to suspend */ static void suspend_god (struct GetOrderData *god) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Suspending GET /orders/%s\n", god->order_id); TMH_long_poll_suspend (god->order_id, god->hc->instance, &god->sc, god->sc.awaiting_refund ? &god->sc.refund_expected : NULL); } /** * Create a taler://refund/ URI for the given @a con and @a order_id * and @a instance_id. * * @param con HTTP connection * @param order_id the order id * @param instance_id instance, may be "default" * @return corresponding taler://refund/ URI, or NULL on missing "host" */ static char * make_taler_refund_uri (struct MHD_Connection *con, const char *order_id, const char *instance_id) { const char *host; const char *forwarded_host; const char *uri_path; struct GNUNET_Buffer buf = { 0 }; host = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "Host"); forwarded_host = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "X-Forwarded-Host"); uri_path = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "X-Forwarded-Prefix"); if (NULL != forwarded_host) host = forwarded_host; if (NULL == host) { GNUNET_break (0); return NULL; } GNUNET_assert (NULL != instance_id); GNUNET_assert (NULL != order_id); GNUNET_buffer_write_str (&buf, "taler"); if (GNUNET_NO == TALER_mhd_is_https (con)) GNUNET_buffer_write_str (&buf, "+http"); GNUNET_buffer_write_str (&buf, "://refund/"); GNUNET_buffer_write_str (&buf, host); if (NULL != uri_path) GNUNET_buffer_write_path (&buf, uri_path); if (0 != strcmp ("default", instance_id)) { GNUNET_buffer_write_path (&buf, "instances"); GNUNET_buffer_write_path (&buf, instance_id); } GNUNET_buffer_write_path (&buf, order_id); GNUNET_buffer_write_path (&buf, ""); // Trailing slash return GNUNET_buffer_reap_str (&buf); } /** * Create a http(s) URL for the given @a con and @a order_id * and @a instance_id to display the /orders/{order_id} page. * * @param con HTTP connection * @param order_id the order id * @param session_id the session id * @param instance_id instance, may be "default" * @param claim_token claim token for the order, may be NULL * @return corresponding http(s):// URL, or NULL on missing "host" */ char * TMH_make_order_status_url (struct MHD_Connection *con, const char *order_id, const char *session_id, const char *instance_id, struct TALER_ClaimTokenP *claim_token) { const char *host; const char *forwarded_host; const char *uri_path; struct GNUNET_Buffer buf = { 0 }; bool have_claim_token = false; host = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "Host"); forwarded_host = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "X-Forwarded-Host"); uri_path = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "X-Forwarded-Prefix"); if (NULL != forwarded_host) host = forwarded_host; if (NULL == host) { GNUNET_break (0); return NULL; } GNUNET_assert (NULL != instance_id); GNUNET_assert (NULL != order_id); if (GNUNET_NO == TALER_mhd_is_https (con)) GNUNET_buffer_write_str (&buf, "http://"); else GNUNET_buffer_write_str (&buf, "https://"); GNUNET_buffer_write_str (&buf, host); if (NULL != uri_path) GNUNET_buffer_write_path (&buf, uri_path); if (0 != strcmp ("default", instance_id)) { GNUNET_buffer_write_path (&buf, "instances"); GNUNET_buffer_write_path (&buf, instance_id); } GNUNET_buffer_write_path (&buf, "/orders"); GNUNET_buffer_write_path (&buf, order_id); if ((NULL != claim_token) && (GNUNET_NO == GNUNET_is_zero (claim_token))) { GNUNET_buffer_write_str (&buf, "?token="); GNUNET_buffer_write_data_encoded (&buf, (char *) claim_token, sizeof (struct TALER_ClaimTokenP)); have_claim_token = true; } if (NULL != session_id) { if (have_claim_token) GNUNET_buffer_write_str (&buf, "&session_id="); else GNUNET_buffer_write_str (&buf, "?session_id="); GNUNET_buffer_write_str (&buf, session_id); } return GNUNET_buffer_reap_str (&buf); } /** * Create a taler://pay/ URI for the given @a con and @a order_id * and @a session_id and @a instance_id. * * @param con HTTP connection * @param order_id the order id * @param session_id session, may be NULL * @param instance_id instance, may be "default" * @param claim_token claim token for the order, may be NULL * @return corresponding taler://pay/ URI, or NULL on missing "host" */ char * TMH_make_taler_pay_uri (struct MHD_Connection *con, const char *order_id, const char *session_id, const char *instance_id, struct TALER_ClaimTokenP *claim_token) { const char *host; const char *forwarded_host; const char *uri_path; struct GNUNET_Buffer buf = { 0 }; host = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "Host"); forwarded_host = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "X-Forwarded-Host"); uri_path = MHD_lookup_connection_value (con, MHD_HEADER_KIND, "X-Forwarded-Prefix"); if (NULL != forwarded_host) host = forwarded_host; if (NULL == host) { GNUNET_break (0); return NULL; } GNUNET_assert (NULL != instance_id); GNUNET_assert (NULL != order_id); GNUNET_buffer_write_str (&buf, "taler"); if (GNUNET_NO == TALER_mhd_is_https (con)) GNUNET_buffer_write_str (&buf, "+http"); GNUNET_buffer_write_str (&buf, "://pay/"); GNUNET_buffer_write_str (&buf, host); if (NULL != uri_path) GNUNET_buffer_write_path (&buf, uri_path); if (0 != strcmp ("default", instance_id)) { GNUNET_buffer_write_path (&buf, "instances"); GNUNET_buffer_write_path (&buf, instance_id); } GNUNET_buffer_write_path (&buf, order_id); GNUNET_buffer_write_path (&buf, (session_id == NULL) ? "" : session_id); if ((NULL != claim_token) && (GNUNET_NO == GNUNET_is_zero (claim_token))) { GNUNET_buffer_write_str (&buf, "?c="); GNUNET_buffer_write_data_encoded (&buf, (char *) claim_token, sizeof (struct TALER_ClaimTokenP)); } return GNUNET_buffer_reap_str (&buf); } /** * Return the order summary of the contract of @a god in the * preferred language of the HTTP client. * * @param god order to extract summary from * @return NULL if no summary was provided in the contract */ static const char * get_order_summary (struct GetOrderData *god) { const char *language_pattern; const char *ret; language_pattern = MHD_lookup_connection_value (god->sc.con, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT_LANGUAGE); if (NULL == language_pattern) language_pattern = "en"; ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms, language_pattern, "summary")); if (NULL == ret) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No order summary found!\n"); ret = ""; } return ret; } /** * The client did not yet pay, send it the payment request. * * @param god check pay request context * @param already_paid_order_id if for the fulfillment URI there is * already a paid order, this is the order ID to redirect * the wallet to; NULL if not applicable * @return #MHD_YES on success */ static MHD_RESULT send_pay_request (struct GetOrderData *god, const char *already_paid_order_id) { MHD_RESULT ret; char *taler_pay_uri; char *order_status_url; struct GNUNET_TIME_Relative remaining; remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); if (0 != remaining.rel_value_us) { /* long polling: do not queue a response, suspend connection instead */ suspend_god (god); return MHD_YES; } /* Check if resource_id has been paid for in the same session * with another order_id. */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Sending payment request in /poll-payment\n"); taler_pay_uri = TMH_make_taler_pay_uri (god->sc.con, god->order_id, god->session_id, god->hc->instance->settings.id, &god->claim_token); order_status_url = TMH_make_order_status_url (god->sc.con, god->order_id, god->session_id, god->hc->instance->settings.id, &god->claim_token); if (god->generate_html) { char *qr; if ( (NULL != already_paid_order_id) && (NULL != god->fulfillment_url) ) { struct MHD_Response *reply; MHD_RESULT ret; reply = MHD_create_response_from_buffer (0, NULL, MHD_RESPMEM_PERSISTENT); if (NULL == reply) { GNUNET_break (0); return MHD_NO; } GNUNET_break (MHD_YES == MHD_add_response_header (reply, MHD_HTTP_HEADER_LOCATION, god->fulfillment_url)); ret = MHD_queue_response (god->sc.con, MHD_HTTP_FOUND, reply); MHD_destroy_response (reply); return ret; } qr = TMH_create_qrcode (taler_pay_uri); if (NULL == qr) { GNUNET_break (0); return MHD_NO; } { enum GNUNET_GenericReturnValue res; json_t *context; context = json_pack ("{s:s, s:s, s:s, s:s}", "taler_pay_uri", taler_pay_uri, "order_status_url", order_status_url, "taler_pay_qrcode_svg", qr, "order_summary", get_order_summary (god)); GNUNET_assert (NULL != context); res = TMH_return_from_template (god->sc.con, MHD_HTTP_PAYMENT_REQUIRED, "request_payment", taler_pay_uri, context); if (GNUNET_SYSERR == res) { GNUNET_break (0); ret = MHD_NO; } ret = MHD_YES; json_decref (context); } GNUNET_free (qr); } else { ret = TALER_MHD_reply_json_pack (god->sc.con, MHD_HTTP_PAYMENT_REQUIRED, "{s:s, s:s?, s:s?}", "taler_pay_uri", taler_pay_uri, "fulfillment_url", god->fulfillment_url, "already_paid_order_id", already_paid_order_id); } GNUNET_free (taler_pay_uri); GNUNET_free (order_status_url); return ret; } /** * Function called with detailed information about a refund. * It is responsible for packing up the data to return. * * @param cls closure * @param refund_serial unique serial number of the refund * @param timestamp time of the refund (for grouping of refunds in the wallet UI) * @param coin_pub public coin from which the refund comes from * @param exchange_url URL of the exchange that issued @a coin_pub * @param rtransaction_id identificator of the refund * @param reason human-readable explanation of the refund * @param refund_amount refund amount which is being taken from @a coin_pub * @param pending true if the this refund was not yet processed by the wallet/exchange */ static void process_refunds_cb (void *cls, uint64_t refund_serial, struct GNUNET_TIME_Absolute timestamp, const struct TALER_CoinSpendPublicKeyP *coin_pub, const char *exchange_url, uint64_t rtransaction_id, const char *reason, const struct TALER_Amount *refund_amount, bool pending) { struct GetOrderData *god = cls; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found refund of %s for coin %s with reason `%s' in database\n", TALER_amount2s (refund_amount), TALER_B2S (coin_pub), reason); if (god->refunded) { GNUNET_assert (0 <= TALER_amount_add (&god->refund_amount, &god->refund_amount, refund_amount)); return; } god->refund_amount = *refund_amount; god->refunded = true; god->refund_available |= pending; } /** * Clean up the session state for a GET /orders/$ID request. * * @param cls must be a `struct GetOrderData *` */ static void god_cleanup (void *cls) { struct GetOrderData *god = cls; if (NULL != god->contract_terms) json_decref (god->contract_terms); GNUNET_free (god); } /** * Handle a GET "/orders/$ID" request. * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] hc context with further information about the request * @return MHD result code */ MHD_RESULT TMH_get_orders_ID (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct GetOrderData *god = hc->ctx; const char *order_id = hc->infix; enum GNUNET_DB_QueryStatus qs; bool contract_match = false; bool token_match = false; bool contract_available = false; if (NULL == god) { god = GNUNET_new (struct GetOrderData); hc->ctx = god; hc->cc = &god_cleanup; god->sc.con = connection; god->hc = hc; god->order_id = order_id; { const char *ct; ct = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "token"); if ( (NULL != ct) && (GNUNET_OK != GNUNET_STRINGS_string_to_data (ct, strlen (ct), &god->claim_token, sizeof (god->claim_token))) ) { /* ct has wrong encoding */ GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_PARAMETER_MALFORMED, "token"); } } { const char *cts; cts = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "h_contract"); if ( (NULL != cts) && (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (cts, &god->h_contract_terms)) ) { /* cts has wrong encoding */ GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_PARAMETER_MALFORMED, "h_contract"); } } god->generate_html = TMH_MHD_test_html_desired (connection); { const char *long_poll_timeout_ms; long_poll_timeout_ms = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "timeout_ms"); if ((NULL != long_poll_timeout_ms) && ! god->generate_html) { unsigned int timeout; if (1 != sscanf (long_poll_timeout_ms, "%u", &timeout)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_PARAMETER_MALFORMED, "timeout_ms (must be non-negative number)"); } god->sc.long_poll_timeout = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( GNUNET_TIME_UNIT_MILLISECONDS, timeout)); } else { god->sc.long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS; } } { const char *await_refund_obtained_s; await_refund_obtained_s = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "await_refund_obtained"); god->sc.awaiting_refund_obtained = (NULL != await_refund_obtained_s) ? 0 == strcasecmp (await_refund_obtained_s, "yes") : false; } { const char *min_refund; min_refund = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "refund"); if (NULL != min_refund) { if ( (GNUNET_OK != TALER_string_to_amount (min_refund, &god->sc.refund_expected)) || (0 != strcasecmp (god->sc.refund_expected.currency, TMH_currency) ) ) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_PARAMETER_MALFORMED, "refund"); } god->sc.awaiting_refund = true; } } god->session_id = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "session_id"); } /* end of first-time initialization / sanity checks */ /* Convert order_id to h_contract_terms */ TMH_db->preflight (TMH_db->cls); if (NULL == god->contract_terms) { uint64_t order_serial; qs = TMH_db->lookup_contract_terms (TMH_db->cls, hc->instance->settings.id, order_id, &god->contract_terms, &order_serial); if (0 > qs) { /* single, read-only SQL statements should never cause serialization problems */ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); /* Always report on hard error as well to enable diagnostics */ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR, NULL); } } /* Check client provided the right hash code of the contract terms */ if (NULL != god->contract_terms) { struct GNUNET_HashCode h; contract_available = true; if (GNUNET_OK != TALER_JSON_contract_hash (god->contract_terms, &h)) { GNUNET_break (0); GNUNET_free (god); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_INTERNAL_LOGIC_ERROR, "could not hash contract terms"); } contract_match = (0 == GNUNET_memcmp (&h, &god->h_contract_terms)); if ( (GNUNET_NO == GNUNET_is_zero (&god->h_contract_terms)) && (! contract_match) ) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_FORBIDDEN, TALER_EC_GET_ORDER_WRONG_CONTRACT, NULL); } } if (! contract_match) { struct TALER_ClaimTokenP db_claim_token; struct GNUNET_HashCode unused; qs = TMH_db->lookup_order (TMH_db->cls, hc->instance->settings.id, order_id, &db_claim_token, &unused, (NULL == god->contract_terms) ? &god->contract_terms : NULL); if (0 > qs) { /* single, read-only SQL statements should never cause serialization problems */ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); /* Always report on hard error as well to enable diagnostics */ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR, NULL); } god->unclaimed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && ! contract_available; if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) && (NULL == god->contract_terms) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Unknown order id given: `%s'\n", order_id); return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_GET_ORDERS_ID_UNKNOWN, order_id); } token_match = (0 == GNUNET_memcmp (&db_claim_token, &god->claim_token)); } /* end unclaimed order logic */ if (NULL == god->fulfillment_url) god->fulfillment_url = json_string_value (json_object_get ( god->contract_terms, "fulfillment_url")); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Token match: %d, contract match: %d, unclaimed: %d\n", token_match, contract_match, god->unclaimed); if ( (god->unclaimed) && (! token_match) ) { /* Token wrong, and required because contract is unclaimed */ GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_FORBIDDEN, TALER_EC_MERCHANT_GET_ORDER_INVALID_TOKEN, NULL); } if ( ( (! token_match) || (GNUNET_YES == GNUNET_is_zero (&god->claim_token)) ) && (! contract_match) ) { if (NULL == god->fulfillment_url) { if (GNUNET_NO == GNUNET_is_zero (&god->h_contract_terms)) return TALER_MHD_reply_with_error (connection, MHD_HTTP_FORBIDDEN, TALER_EC_GET_ORDER_WRONG_CONTRACT, NULL); return TALER_MHD_reply_with_error (connection, MHD_HTTP_FORBIDDEN, TALER_EC_MERCHANT_GET_ORDER_INVALID_TOKEN, NULL); } if (god->generate_html) { /* Contract was claimed (maybe by another device), so this client cannot get the status information. Redirect to fulfillment page, where the client may be able to pickup a fresh order -- or might be able authenticate via session ID */ struct MHD_Response *reply; MHD_RESULT ret; reply = MHD_create_response_from_buffer (0, NULL, MHD_RESPMEM_PERSISTENT); if (NULL == reply) { GNUNET_break (0); return MHD_NO; } GNUNET_break (MHD_YES == MHD_add_response_header (reply, MHD_HTTP_HEADER_LOCATION, god->fulfillment_url)); ret = MHD_queue_response (connection, MHD_HTTP_FOUND, reply); MHD_destroy_response (reply); return ret; } /* Need to generate JSON reply */ return TALER_MHD_reply_json_pack (connection, MHD_HTTP_ACCEPTED, "{s:s}", "fulfillment_url", god->fulfillment_url); } if (god->unclaimed) { /* Order is unclaimed, no need to check for payments or even refunds, simply always generate payment request */ return send_pay_request (god, NULL); } if ( (NULL != god->session_id) && (NULL != god->fulfillment_url) ) { /* Check if paid within a session. */ char *already_paid_order_id = NULL; qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls, hc->instance->settings.id, god->fulfillment_url, god->session_id, &already_paid_order_id); if (qs < 0) { /* single, read-only SQL statements should never cause serialization problems */ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); /* Always report on hard error as well to enable diagnostics */ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR, NULL); } if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || (0 != strcmp (order_id, already_paid_order_id)) ) { MHD_RESULT ret; ret = send_pay_request (god, already_paid_order_id); GNUNET_free (already_paid_order_id); return ret; } GNUNET_break (1 == qs); GNUNET_free (already_paid_order_id); } { /* Check if paid. */ struct GNUNET_HashCode h_contract; bool paid; qs = TMH_db->lookup_order_status (TMH_db->cls, hc->instance->settings.id, order_id, &h_contract, &paid); if (0 >= qs) { /* Always report on hard error as well to enable diagnostics */ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR, NULL); } GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); GNUNET_break (0 == GNUNET_memcmp (&h_contract, &god->h_contract_terms)); if (! paid) { return send_pay_request (god, NULL); } } /* At this point, we know the contract was paid. Let's check for refunds. First, clear away refunds found from previous invocations. */ GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (TMH_currency, &god->refund_amount)); qs = TMH_db->lookup_refunds_detailed (TMH_db->cls, hc->instance->settings.id, &god->h_contract_terms, &process_refunds_cb, god); if (0 > qs) { GNUNET_break (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GET_ORDERS_DB_LOOKUP_ERROR, NULL); } if ( ((god->sc.awaiting_refund) && ( (! god->refunded) || (1 != TALER_amount_cmp (&god->refund_amount, &god->sc.refund_expected)) )) || ((god->sc.awaiting_refund_obtained) && (god->refund_available)) ) { /* Client is waiting for a refund larger than what we have, suspend until timeout */ struct GNUNET_TIME_Relative remaining; remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); if (0 != remaining.rel_value_us) { /* yes, indeed suspend */ if (god->sc.awaiting_refund) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Awaiting refund exceeding %s\n", TALER_amount2s (&god->sc.refund_expected)); if (god->sc.awaiting_refund_obtained) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Awaiting pending refunds\n"); suspend_god (god); return MHD_YES; } } /* All operations done, build final response */ { if (god->generate_html) { enum GNUNET_GenericReturnValue res; if (god->refund_available) { char *qr; char *uri; GNUNET_assert (NULL != god->contract_terms); uri = make_taler_refund_uri (god->sc.con, order_id, hc->instance->settings.id); if (NULL == uri) { GNUNET_break (0); return TALER_MHD_reply_with_error (god->sc.con, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_ALLOCATION_FAILURE, "refund URI"); } qr = TMH_create_qrcode (uri); if (NULL == qr) { GNUNET_break (0); GNUNET_free (uri); return TALER_MHD_reply_with_error (god->sc.con, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_ALLOCATION_FAILURE, "qr code"); } { json_t *context; context = json_pack ("{s:s, s:s, s:s, s:s}", "order_summary", get_order_summary (god), "refund_amount", TALER_amount2s (&god->refund_amount), "taler_refund_uri", uri, "taler_refund_qrcode_svg", qr); GNUNET_assert (NULL != context); res = TMH_return_from_template (god->sc.con, MHD_HTTP_OK, "offer_refund", uri, context); json_decref (context); } GNUNET_free (uri); GNUNET_free (qr); } else { json_t *context; context = json_pack ("{s:O, s:s, s:s}", "contract_terms", god->contract_terms, "order_summary", get_order_summary (god), "refund_amount", TALER_amount2s (&god->refund_amount)); GNUNET_assert (NULL != context); res = TMH_return_from_template (god->sc.con, MHD_HTTP_OK, "show_order_details", NULL, context); json_decref (context); } if (GNUNET_SYSERR == res) { GNUNET_break (0); return MHD_NO; } return MHD_YES; } else { return TALER_MHD_reply_json_pack ( connection, MHD_HTTP_OK, "{s:b, s:b, s:o}", "refunded", god->refunded, "refund_pending", god->refund_available, "refund_amount", TALER_JSON_from_amount (&god->refund_amount)); } } } /* end of taler-merchant-httpd_get-orders-ID.c */