From 4a008c1e1356535cec84126a31f27d084393bdbd Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 22 Jul 2021 13:42:49 -0300 Subject: worshop fixes --- .../taler-merchant-httpd_post-orders-ID-pay.c | 439 ++++++++++++--------- 1 file changed, 248 insertions(+), 191 deletions(-) (limited to 'src') diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c index 31ccbbea..71457c85 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -39,6 +39,11 @@ */ #define MAX_RETRIES 5 +/** + * Maximum number of coins that we allow per transaction + */ +#define MAX_COIN_ALLOWED_COINS 1024 + /** * Information we keep for an individual call to the pay handler. */ @@ -196,7 +201,7 @@ struct PayContext /** * Transaction ID given in @e root. */ - char *order_id; + const char *order_id; /** * Fulfillment URL from the contract, or NULL if we don't have one. @@ -342,7 +347,7 @@ struct PayContext * #GNUNET_SYSERR if @e connection was resumed to as * part of #MH_force_pc_resume during shutdown. */ - int suspended; + enum GNUNET_GenericReturnValue suspended; /** * true if we already tried a forced /keys download. @@ -526,7 +531,6 @@ pay_context_cleanup (void *cls) MHD_destroy_response (pc->response); pc->response = NULL; } - GNUNET_free (pc->order_id); GNUNET_free (pc->fulfillment_url); GNUNET_free (pc->session_id); GNUNET_CONTAINER_DLL_remove (pc_head, @@ -547,14 +551,14 @@ find_next_exchange (struct PayContext *pc); /** - * Begin of the DB transaction. If required (from + * Execute the DB transaction. If required (from * soft/serialization errors), the transaction can be * restarted here. * * @param pc payment context to transact */ static void -begin_transaction (struct PayContext *pc); +execute_pay_transaction (struct PayContext *pc); /** @@ -579,7 +583,6 @@ deposit_cb (void *cls, { struct DepositConfirmation *dc = cls; struct PayContext *pc = dc->pc; - enum GNUNET_DB_QueryStatus qs; dc->dh = NULL; GNUNET_assert (GNUNET_YES == pc->suspended); @@ -632,6 +635,7 @@ deposit_cb (void *cls, /* Forward error, adding the "coin_pub" for which the error was being generated */ if (TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS == hr->ec) + { resume_pay_with_response ( pc, MHD_HTTP_CONFLICT, @@ -650,7 +654,9 @@ deposit_cb (void *cls, GNUNET_JSON_from_data_auto (&dc->coin_pub), "exchange_reply", hr->reply)); + } else + { resume_pay_with_response ( pc, MHD_HTTP_BAD_GATEWAY, @@ -669,6 +675,7 @@ deposit_cb (void *cls, GNUNET_JSON_from_data_auto (&dc->coin_pub), "exchange_reply", hr->reply)); + } } return; } @@ -680,37 +687,41 @@ deposit_cb (void *cls, GNUNET_h2s (&pc->h_contract_terms), pc->hc->instance->settings.id); TMH_db->preflight (TMH_db->cls); - qs = TMH_db->insert_deposit (TMH_db->cls, - pc->hc->instance->settings.id, - deposit_timestamp, - &pc->h_contract_terms, - &dc->coin_pub, - dc->exchange_url, - &dc->amount_with_fee, - &dc->deposit_fee, - &dc->refund_fee, - &dc->wire_fee, - &pc->wm->h_wire, - exchange_sig, - exchange_pub); - if (0 > qs) { - /* Special report if retries insufficient */ - abort_deposit (pc); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->insert_deposit (TMH_db->cls, + pc->hc->instance->settings.id, + deposit_timestamp, + &pc->h_contract_terms, + &dc->coin_pub, + dc->exchange_url, + &dc->amount_with_fee, + &dc->deposit_fee, + &dc->refund_fee, + &dc->wire_fee, + &pc->wm->h_wire, + exchange_sig, + exchange_pub); + if (0 > qs) { - begin_transaction (pc); + /* Special report if retries insufficient */ + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + execute_pay_transaction (pc); + return; + } + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + /* Forward error including 'proof' for the body */ + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "deposit"); return; } - /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - /* Forward error including 'proof' for the body */ - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "deposit"); - return; } + dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */ pc->pending--; @@ -764,7 +775,6 @@ process_pay_with_exchange (void *cls, if ( (MHD_HTTP_OK != hr->http_status) || (NULL == exchange_handle) ) { - GNUNET_break_op (0); resume_pay_with_response ( pc, MHD_HTTP_BAD_GATEWAY, @@ -932,31 +942,31 @@ find_next_exchange (struct PayContext *pc) { struct DepositConfirmation *dc = &pc->dc[i]; - if (! dc->found_in_db) + if (dc->found_in_db) + continue; + + pc->current_exchange = dc->exchange_url; + pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange, + pc->wm->wire_method, + GNUNET_NO, + &process_pay_with_exchange, + pc); + if (NULL == pc->fo) { - pc->current_exchange = dc->exchange_url; - pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange, - pc->wm->wire_method, - GNUNET_NO, - &process_pay_with_exchange, - pc); - if (NULL == pc->fo) - { - GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED, - "Failed to lookup exchange by URL"); - return; - } + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED, + "Failed to lookup exchange by URL"); return; } + return; } pc->current_exchange = NULL; /* We are done with all the HTTP requests, go back and try the 'big' database transaction! (It should work now!) */ GNUNET_assert (0 == pc->pending); - begin_transaction (pc); + execute_pay_transaction (pc); } @@ -1097,6 +1107,7 @@ check_payment_sufficient (struct PayContext *pc) MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, "Overflow adding up amounts"); + return false; } if (1 == TALER_amount_cmp (&dc->deposit_fee, @@ -1121,34 +1132,35 @@ check_payment_sufficient (struct PayContext *pc) new_exchange = false; break; } - if (new_exchange) + + if (!new_exchange) + continue; + + if (GNUNET_OK != + TALER_amount_cmp_currency (&total_wire_fee, + &dc->wire_fee)) { - if (GNUNET_OK != - TALER_amount_cmp_currency (&total_wire_fee, - &dc->wire_fee)) - { - GNUNET_break_op (0); - resume_pay_with_error (pc, - MHD_HTTP_PRECONDITION_FAILED, - TALER_EC_GENERIC_CURRENCY_MISMATCH, - total_wire_fee.currency); - return false; - } - if (0 > - TALER_amount_add (&total_wire_fee, - &total_wire_fee, - &dc->wire_fee)) - { - GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED, - "could not add exchange wire fee to total"); - return false; - } + GNUNET_break_op (0); + resume_pay_with_error (pc, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + total_wire_fee.currency); + return false; + } + if (0 > + TALER_amount_add (&total_wire_fee, + &total_wire_fee, + &dc->wire_fee)) + { + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED, + "could not add exchange wire fee to total"); + return false; } } - } + } /* deposit loop */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Amount received from wallet: %s\n", @@ -1175,8 +1187,9 @@ check_payment_sufficient (struct PayContext *pc) TALER_amount_cmp_currency (&total_wire_fee, &pc->max_wire_fee)) { + GNUNET_break (0); resume_pay_with_error (pc, - MHD_HTTP_PRECONDITION_FAILED, + MHD_HTTP_CONFLICT, TALER_EC_GENERIC_CURRENCY_MISMATCH, total_wire_fee.currency); return false; @@ -1278,6 +1291,7 @@ check_payment_sufficient (struct PayContext *pc) if (-1 < TALER_amount_cmp (&acc_amount, &total_needed)) { + GNUNET_break_op (0); resume_pay_with_error (pc, MHD_HTTP_PAYMENT_REQUIRED, TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED, @@ -1305,11 +1319,13 @@ check_payment_sufficient (struct PayContext *pc) return true; } - +/** + * + * + */ static void -begin_transaction (struct PayContext *pc) +execute_pay_transaction (struct PayContext *pc) { - enum GNUNET_DB_QueryStatus qs; struct TMH_HandlerContext *hc = pc->hc; const char *instance_id = hc->instance->settings.id; bool refunded; @@ -1356,53 +1372,60 @@ begin_transaction (struct PayContext *pc) return; } - /* Check if some of these coins already succeeded for _this_ contract. */ - qs = TMH_db->lookup_deposits (TMH_db->cls, - instance_id, - &pc->h_contract_terms, - &check_coin_paid, - pc); - if (0 > qs) { - TMH_db->rollback (TMH_db->cls); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + enum GNUNET_DB_QueryStatus qs; + + /* Check if some of these coins already succeeded for _this_ contract. */ + qs = TMH_db->lookup_deposits (TMH_db->cls, + instance_id, + &pc->h_contract_terms, + &check_coin_paid, + pc); + if (0 > qs) { - begin_transaction (pc); + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + execute_pay_transaction (pc); + return; + } + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup deposits"); return; } - /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "deposits"); - return; } - /* Check if we refunded some of the coins */ - qs = TMH_db->lookup_refunds (TMH_db->cls, - instance_id, - &pc->h_contract_terms, - &check_coin_refunded, - pc); - if (0 > qs) + { - TMH_db->rollback (TMH_db->cls); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + enum GNUNET_DB_QueryStatus qs; + /* Check if we refunded some of the coins */ + qs = TMH_db->lookup_refunds (TMH_db->cls, + instance_id, + &pc->h_contract_terms, + &check_coin_refunded, + pc); + if (0 > qs) { - begin_transaction (pc); + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + execute_pay_transaction (pc); + return; + } + /* Always report on hard error as well to enable diagnostics */ + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup refunds"); return; } - /* Always report on hard error as well to enable diagnostics */ - GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup refunds"); - return; + refunded = (qs > 0); } - refunded = (qs > 0); - /* Check if there are coins that still need to be processed */ if (0 != pc->pending) @@ -1430,41 +1453,51 @@ begin_transaction (struct PayContext *pc) "Order `%s' (%s) was fully paid\n", pc->order_id, GNUNET_h2s (&pc->h_contract_terms)); - qs = TMH_db->mark_contract_paid (TMH_db->cls, - instance_id, - &pc->h_contract_terms, - pc->session_id); - if (qs < 0) { - TMH_db->rollback (TMH_db->cls); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->mark_contract_paid (TMH_db->cls, + instance_id, + &pc->h_contract_terms, + pc->session_id); + if (qs < 0) { - begin_transaction (pc); + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + execute_pay_transaction (pc); + return; + } + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "mark contract paid"); return; } - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "mark contract paid"); - return; } - /* Now commit! */ - qs = TMH_db->commit (TMH_db->cls); - if (0 > qs) { - /* commit failed */ - TMH_db->rollback (TMH_db->cls); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + enum GNUNET_DB_QueryStatus qs; + + /* Now commit! */ + qs = TMH_db->commit (TMH_db->cls); + if (0 > qs) { - begin_transaction (pc); + /* commit failed */ + TMH_db->rollback (TMH_db->cls); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + execute_pay_transaction (pc); + return; + } + GNUNET_break (0); + resume_pay_with_error (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); return; } - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - NULL); - return; } /* Notify clients that have been waiting for the payment to succeed */ @@ -1509,15 +1542,7 @@ begin_transaction (struct PayContext *pc) resp = json_pack ("{s:o}", "sig", GNUNET_JSON_from_data_auto (&sig)); - if (NULL == resp) - { - GNUNET_break (0); - resume_pay_with_error (pc, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, - "Could not build final response"); - return; - } + GNUNET_assert (NULL != resp); resume_pay_with_response (pc, MHD_HTTP_OK, TALER_MHD_make_json (resp)); @@ -1580,15 +1605,29 @@ parse_pay (struct MHD_Connection *connection, pc->session_id = GNUNET_strdup (""); } - if ( (! json_is_array (coins)) || - (0 == (pc->coins_cnt = json_array_size (coins))) ) + if (! json_is_array (coins)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error (connection, + return (MHD_YES == TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MISSING, - "'coins' array is empty or not even an array"); + "'coins' must be an array")) + ? GNUNET_NO + : GNUNET_SYSERR; + } + + pc->coins_cnt = json_array_size (coins); + if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return (MHD_YES == TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "'coins' array too long")) + ? GNUNET_NO + : GNUNET_SYSERR; } /* note: 1 coin = 1 deposit confirmation expected */ @@ -1603,49 +1642,51 @@ parse_pay (struct MHD_Connection *connection, { struct DepositConfirmation *dc = &pc->dc[coins_index]; const char *exchange_url; - enum GNUNET_GenericReturnValue res; struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &dc->coin_sig), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &dc->coin_pub), + TALER_JSON_spec_denomination_signature ("ub_sig", + &dc->ub_sig), GNUNET_JSON_spec_fixed_auto ("h_denom", &dc->h_denom), TALER_JSON_spec_amount ("contribution", &dc->amount_with_fee), GNUNET_JSON_spec_string ("exchange_url", &exchange_url), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &dc->coin_pub), - TALER_JSON_spec_denomination_signature ("ub_sig", - &dc->ub_sig), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &dc->coin_sig), GNUNET_JSON_spec_end () }; - res = TALER_MHD_parse_json_data (connection, - coin, - ispec); + enum GNUNET_GenericReturnValue res = TALER_MHD_parse_json_data (connection, + coin, + ispec); if (GNUNET_YES != res) { - GNUNET_JSON_parse_free (spec); GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); return res; } dc->exchange_url = GNUNET_strdup (exchange_url); dc->index = coins_index; dc->pc = pc; + + if (0 != + strcasecmp (dc->amount_with_fee.currency, + TMH_currency)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + TMH_currency); + } } } GNUNET_JSON_parse_free (spec); } - /* copy order ID */ - { - const char *order_id = hc->infix; - - GNUNET_assert (NULL != order_id); - GNUNET_assert (NULL == pc->order_id); - pc->order_id = GNUNET_strdup (order_id); - } - /* obtain contract terms */ { enum GNUNET_DB_QueryStatus qs; @@ -1720,36 +1761,36 @@ parse_pay (struct MHD_Connection *connection, /* Get details from contract and check fundamentals */ { + const char *fulfillment_url = NULL; struct GNUNET_JSON_Specification espec[] = { + TALER_JSON_spec_amount ("amount", + &pc->amount), + GNUNET_JSON_spec_mark_optional( + GNUNET_JSON_spec_string ("fulfillment_url", + &fulfillment_url)), + TALER_JSON_spec_amount ("max_fee", + &pc->max_fee), + TALER_JSON_spec_amount ("max_wire_fee", + &pc->max_wire_fee), + GNUNET_JSON_spec_uint32 ("wire_fee_amortization", + &pc->wire_fee_amortization), + TALER_JSON_spec_absolute_time ("timestamp", + &pc->timestamp), TALER_JSON_spec_absolute_time ("refund_deadline", &pc->refund_deadline), TALER_JSON_spec_absolute_time ("pay_deadline", &pc->pay_deadline), TALER_JSON_spec_absolute_time ("wire_transfer_deadline", &pc->wire_transfer_deadline), - TALER_JSON_spec_absolute_time ("timestamp", - &pc->timestamp), - TALER_JSON_spec_amount ("max_fee", - &pc->max_fee), - TALER_JSON_spec_amount ("amount", - &pc->amount), GNUNET_JSON_spec_fixed_auto ("h_wire", &pc->h_wire), - GNUNET_JSON_spec_uint32 ("wire_fee_amortization", - &pc->wire_fee_amortization), - TALER_JSON_spec_amount ("max_wire_fee", - &pc->max_wire_fee), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; - const char *fulfillment_url; res = TALER_MHD_parse_internal_json_data (connection, contract_terms, espec); - fulfillment_url - = json_string_value (json_object_get (contract_terms, - "fulfillment_url")); if (NULL != fulfillment_url) pc->fulfillment_url = GNUNET_strdup (fulfillment_url); json_decref (contract_terms); @@ -1758,6 +1799,20 @@ parse_pay (struct MHD_Connection *connection, GNUNET_break (0); return res; } + + if ((0 != strcasecmp (pc->amount.currency, + TMH_currency)) || + (0 != strcasecmp (pc->max_fee.currency, + TMH_currency) || + (0 != strcasecmp (pc->max_wire_fee.currency, + TMH_currency)))) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + TMH_currency); + } } if (pc->wire_transfer_deadline.abs_value_us < @@ -1771,8 +1826,7 @@ parse_pay (struct MHD_Connection *connection, NULL); } - if (pc->pay_deadline.abs_value_us < - GNUNET_TIME_absolute_get ().abs_value_us) + if (0 == GNUNET_TIME_absolute_get_remaining (pc->pay_deadline).rel_value_us) { /* too late */ return (MHD_YES == @@ -1841,6 +1895,8 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, { struct PayContext *pc = hc->ctx; + GNUNET_assert (NULL != hc->infix); + if (NULL == pc) { pc = GNUNET_new (struct PayContext); @@ -1849,11 +1905,13 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, pc); pc->connection = connection; pc->hc = hc; + pc->order_id = hc->infix; hc->ctx = pc; hc->cc = &pay_context_cleanup; } if (GNUNET_SYSERR == pc->suspended) return MHD_NO; /* during shutdown, we don't generate any more replies */ + GNUNET_assert (GNUNET_NO == pc->suspended); if (0 != pc->response_code) { MHD_RESULT res; @@ -1888,7 +1946,6 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, } /* Payment not finished, suspend while we interact with the exchange */ - GNUNET_assert (GNUNET_NO == pc->suspended); MHD_suspend_connection (connection); pc->suspended = GNUNET_YES; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -1898,7 +1955,7 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh, GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt), &handle_pay_timeout, pc); - begin_transaction (pc); + execute_pay_transaction (pc); return MHD_YES; } -- cgit v1.2.3