diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_check-payment.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_check-payment.c | 315 |
1 files changed, 204 insertions, 111 deletions
diff --git a/src/backend/taler-merchant-httpd_check-payment.c b/src/backend/taler-merchant-httpd_check-payment.c index 340770fe..11f6ff91 100644 --- a/src/backend/taler-merchant-httpd_check-payment.c +++ b/src/backend/taler-merchant-httpd_check-payment.c @@ -66,6 +66,115 @@ process_refunds_cb (void *cls, /** + * The client did not yet pay, send it the payment request. + * + * @param connection connection to send on + * @param final_contract_url where to get the contract + * @param session_id session of the client + * @param resource_url where the resource will be (?), can be NULL! + * @param h_contract_terms_str hash of the contract terms, stringified + * @return #MHD_YES on success + */ +static int +send_pay_request (struct MHD_Connection *connection, + const char *final_contract_url, + const char *session_id, + const char *resource_url, + const char *h_contract_terms_str) +{ + int ret; + char *url = TALER_url_absolute_mhd (connection, + "public/trigger-pay", + "contract_url", final_contract_url, + "session_id", session_id, + "resource_url", resource_url, + "h_contract_terms", h_contract_terms_str, + NULL); + GNUNET_assert (NULL != url); + ret = TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:s, s:b}", + "payment_redirect_url", + url, + "paid", + 0); + GNUNET_free (url); + return ret; +} + + +/** + * Check that we are aware of @a order_id and if so request the payment, + * otherwise generate an error response. + * + * @param connection where to send the response + * @param mi the merchant's instance + * @param final_contract_url where to redirect for the contract + * @param session_id the session_id + * @param resource_url where the resource will be (?), can be NULL! + * @param order_id the order to look up + * @return #MHD_YES on success + */ +static int +check_order_and_request_payment (struct MHD_Connection *connection, + struct MerchantInstance *mi, + const char *final_contract_url, + const char *session_id, + const char *resource_url, + const char *order_id) +{ + enum GNUNET_DB_QueryStatus qs; + json_t *contract_terms; + struct GNUNET_HashCode h_contract_terms; + char *h_contract_terms_str; + int ret; + + qs = db->find_order (db->cls, + &contract_terms, + order_id, + &mi->pubkey); + 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 TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR, + "db error fetching order"); + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + return TMH_RESPONSE_reply_not_found (connection, + TALER_EC_CHECK_PAYMENT_ORDER_ID_UNKNOWN, + "unknown order_id"); + } + if (GNUNET_OK != + TALER_JSON_hash (contract_terms, + &h_contract_terms)) + { + GNUNET_break (0); + json_decref (contract_terms); + return TMH_RESPONSE_reply_internal_error (connection, + TALER_EC_CHECK_PAYMENT_FAILED_COMPUTE_PROPOSAL_HASH, + "Failed to hash proposal"); + } + /* Offer was not picked up yet, but we ensured that it exists */ + h_contract_terms_str = GNUNET_STRINGS_data_to_string_alloc (&h_contract_terms, + sizeof (struct GNUNET_HashCode)); + ret = send_pay_request (connection, + final_contract_url, + session_id, + resource_url, + h_contract_terms_str); + GNUNET_free_non_null (h_contract_terms_str); + json_decref (contract_terms); + return ret; +} + + +/** * Manages a /check-payment call, checking the status * of a payment and, if necessary, constructing the URL * for a payment redirect URL. @@ -90,54 +199,41 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, const char *session_sig_str; const char *instance_str; const char *resource_url; - char *final_contract_url = NULL; - char *h_contract_terms_str = NULL; + char *final_contract_url; + char *h_contract_terms_str; struct MerchantInstance *mi; enum GNUNET_DB_QueryStatus qs; json_t *contract_terms; struct GNUNET_HashCode h_contract_terms; struct TALER_Amount refund_amount; char *last_session_id; + int ret; + int refunded; - order_id = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "order_id"); - contract_url = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "contract_url"); - session_id = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "session_id"); - session_sig_str = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "session_sig"); instance_str = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "instance"); - resource_url = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "resource_url"); - if (NULL == instance_str) instance_str = "default"; - mi = TMH_lookup_instance (instance_str); if (NULL == mi) return TMH_RESPONSE_reply_bad_request (connection, TALER_EC_CHECK_PAYMENT_INSTANCE_UNKNOWN, "merchant instance unknown"); - + order_id = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "order_id"); if (NULL == order_id) { - if (NULL == contract_url) - return TMH_RESPONSE_reply_bad_request (connection, - TALER_EC_PARAMETER_MISSING, - "either order_id or contract_url must be given"); - goto do_pay; - // No order_id given, redirect to a page that gives the wallet a new - // contract. + /* order_id is required but missing */ + GNUNET_break_op (0); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MISSING, + "order_id required"); } - + contract_url = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "contract_url"); if (NULL == contract_url) { final_contract_url = TALER_url_absolute_mhd (connection, @@ -151,32 +247,45 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, { final_contract_url = GNUNET_strdup (contract_url); } - + resource_url = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "resource_url"); + /* NULL is allowed for resource_url! */ + session_id = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "session_id"); + session_sig_str = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "session_sig"); if (NULL != session_id) { struct GNUNET_CRYPTO_EddsaSignature sig; struct TALER_MerchantPaySessionSigPS mps; - // If the session id is given, the frontend wants us - // to verify the session signature. + /* If the session id is given, the frontend wants us + to verify the session signature. */ if (NULL == session_sig_str) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pay session signature required but missing\n"); - goto do_pay; + /* pay session signature required but missing */ + GNUNET_break_op (0); + GNUNET_free (final_contract_url); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MISSING, + "session_sig required if session_id given"); } - if (GNUNET_OK != GNUNET_STRINGS_string_to_data (session_sig_str, strlen (session_sig_str), &sig, sizeof (struct GNUNET_CRYPTO_EddsaSignature))) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pay session signature malformed\n"); - goto do_pay; + GNUNET_break_op (0); + GNUNET_free (final_contract_url); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_PARAMETER_MALFORMED, + "session_sig malformed"); } mps.purpose.size = htonl (sizeof (struct TALER_MerchantPaySessionSigPS)); mps.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAY_SESSION); - GNUNET_assert (NULL != order_id); - GNUNET_assert (NULL != session_id); GNUNET_CRYPTO_hash (order_id, strlen (order_id), &mps.h_order_id); GNUNET_CRYPTO_hash (session_id, strlen (session_id), &mps.h_session_id); if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAY_SESSION, @@ -184,13 +293,15 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, &sig, &mi->pubkey.eddsa_pub)) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "pay session signature invalid\n"); - goto do_pay; + /* pay session signature invalid */ + GNUNET_break_op (0); + GNUNET_free (final_contract_url); + return TMH_RESPONSE_reply_bad_request (connection, + TALER_EC_CHECK_PAYMENT_SESSION_SIGNATURE_INVALID, + "session_sig fails to verify"); } } - GNUNET_assert (NULL != order_id); - db->preflight (db->cls); qs = db->find_contract_terms (db->cls, &contract_terms, @@ -204,35 +315,22 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, 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); + GNUNET_free (final_contract_url); return TMH_RESPONSE_reply_internal_error (connection, - TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR, - "db error fetching contract terms"); + TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR, + "db error fetching contract terms"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - qs = db->find_order (db->cls, - &contract_terms, - order_id, - &mi->pubkey); - 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 TMH_RESPONSE_reply_internal_error (connection, - TALER_EC_CHECK_PAYMENT_DB_FETCH_ORDER_ERROR, - "db error fetching order"); - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - return TMH_RESPONSE_reply_not_found (connection, - TALER_EC_CHECK_PAYMENT_ORDER_ID_UNKNOWN, - "unknown order id"); - } - /* Offer was not picked up yet, but we ensured that it exists */ - goto do_pay; + /* Check that we're at least aware of the order */ + ret = check_order_and_request_payment (connection, + mi, + final_contract_url, + session_id, + resource_url, + order_id); + GNUNET_free (final_contract_url); + return ret; } GNUNET_assert (NULL != contract_terms); @@ -243,6 +341,8 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, &h_contract_terms)) { GNUNET_break (0); + json_decref (contract_terms); + GNUNET_free (final_contract_url); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_CHECK_PAYMENT_FAILED_COMPUTE_PROPOSAL_HASH, "Failed to hash proposal"); @@ -251,7 +351,6 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, h_contract_terms_str = GNUNET_STRINGS_data_to_string_alloc (&h_contract_terms, sizeof (struct GNUNET_HashCode)); - /* Check if paid */ { json_t *xcontract_terms = NULL; @@ -265,6 +364,8 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, /* Always report on hard error as well to enable diagnostics */ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); GNUNET_free_non_null (h_contract_terms_str); + GNUNET_free (final_contract_url); + json_decref (contract_terms); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, "Merchant database error"); @@ -272,7 +373,16 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, if (0 == qs) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "not paid yet\n"); - goto do_pay; + ret = send_pay_request (connection, + final_contract_url, + session_id, + resource_url, + h_contract_terms_str); + GNUNET_free_non_null (h_contract_terms_str); + GNUNET_free (final_contract_url); + json_decref (contract_terms); + return ret; + } GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); GNUNET_assert (NULL != xcontract_terms); @@ -285,20 +395,26 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, TALER_JSON_spec_amount ("amount", &amount), GNUNET_JSON_spec_end () }; + if (GNUNET_OK != GNUNET_JSON_parse (contract_terms, spec, NULL, NULL)) + { + GNUNET_free_non_null (h_contract_terms_str); + GNUNET_free (final_contract_url); + json_decref (contract_terms); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_CHECK_PAYMENT_DB_FETCH_CONTRACT_TERMS_ERROR, "Merchant database error (contract terms corrupted)"); + } TALER_amount_get_zero (amount.currency, &refund_amount); } for (unsigned int i=0;i<MAX_RETRIES;i++) { qs = db->get_refunds_from_contract_terms_hash (db->cls, - &mi->pubkey, - &h_contract_terms, - &process_refunds_cb, - &refund_amount); + &mi->pubkey, + &h_contract_terms, + &process_refunds_cb, + &refund_amount); if (GNUNET_DB_STATUS_SOFT_ERROR != qs) break; } @@ -307,49 +423,26 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database hard error on refunds_from_contract_terms_hash lookup: %s\n", GNUNET_h2s (&h_contract_terms)); + GNUNET_free_non_null (h_contract_terms_str); + GNUNET_free (final_contract_url); + json_decref (contract_terms); return TMH_RESPONSE_reply_internal_error (connection, TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR, "Merchant database error"); } - GNUNET_free_non_null (h_contract_terms_str); - { - int refunded = (0 != refund_amount.value) || (0 != refund_amount.fraction); - int res; - res = TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:o s:b, s:b, s:o, s:s}", - "contract_terms", contract_terms, - "paid", 1, - "refunded", refunded, - "refund_amount", TALER_JSON_from_amount (&refund_amount), - "last_session_id", last_session_id); - GNUNET_free_non_null (final_contract_url); - return res; - } + refunded = (0 != refund_amount.value) || (0 != refund_amount.fraction); -do_pay: - { - char *url = TALER_url_absolute_mhd (connection, - "public/trigger-pay", - "contract_url", final_contract_url, - "session_id", session_id, - "resource_url", resource_url, - "h_contract_terms", h_contract_terms_str, - NULL); - GNUNET_assert (NULL != url); - int ret = TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_OK, - "{s:s, s:b}", - "payment_redirect_url", - url, - "paid", - 0); - GNUNET_free_non_null (h_contract_terms_str); - GNUNET_free_non_null (final_contract_url); - json_decref (contract_terms); - GNUNET_free (url); - return ret; - } + ret = TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_OK, + "{s:o, s:b, s:b, s:o, s:s}", + "contract_terms", contract_terms, + "paid", 1, + "refunded", refunded, + "refund_amount", TALER_JSON_from_amount (&refund_amount), + "last_session_id", last_session_id); + GNUNET_free (final_contract_url); + GNUNET_free (last_session_id); + return ret; } |