summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-08-25 23:57:39 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-08-25 23:57:39 +0200
commitdeac44203154413eb11af52aa5fab6743a9f5724 (patch)
tree9a9d12afaad72dbb43aa534b8001fb8b61359731
parent1720a924491f278580bc31182bee8f8190bc1081 (diff)
downloadmerchant-deac44203154413eb11af52aa5fab6743a9f5724.tar.gz
merchant-deac44203154413eb11af52aa5fab6743a9f5724.tar.bz2
merchant-deac44203154413eb11af52aa5fab6743a9f5724.zip
implement re-pointing to an already paid order
This is mostly useful if a resource should be displayed on one device, but the payment happens on another device.
-rw-r--r--src/backend/taler-merchant-httpd_check-payment.c56
-rw-r--r--src/backend/taler-merchant-httpd_pay.c45
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c108
-rw-r--r--src/include/taler_merchantdb_plugin.h37
4 files changed, 233 insertions, 13 deletions
diff --git a/src/backend/taler-merchant-httpd_check-payment.c b/src/backend/taler-merchant-httpd_check-payment.c
index 2e6cea91..9ca445b4 100644
--- a/src/backend/taler-merchant-httpd_check-payment.c
+++ b/src/backend/taler-merchant-httpd_check-payment.c
@@ -73,6 +73,7 @@ process_refunds_cb (void *cls,
* @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
+ * @param mi merchant instance
* @return #MHD_YES on success
*/
static int
@@ -80,26 +81,55 @@ 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)
+ const char *h_contract_terms_str,
+ const struct MerchantInstance *mi)
{
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);
+ int qs;
+ char *url;
+ char *already_paid_order_id = NULL;
+
+ if ( (NULL != session_id) && (NULL != resource_url) )
+ {
+ qs = db->find_session_info (db->cls,
+ &already_paid_order_id,
+ session_id,
+ resource_url,
+ &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 pay session info");
+ }
+ }
+
+ 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:s, s:b}",
+ "{s:s, s:s, s:b, s:s?}",
"payment_redirect_url",
url,
"contract_url",
final_contract_url,
"paid",
- 0);
+ 0,
+ "already_paid_order_id",
+ already_paid_order_id
+ );
+ GNUNET_free_non_null (already_paid_order_id);
GNUNET_free (url);
return ret;
}
@@ -169,7 +199,8 @@ check_order_and_request_payment (struct MHD_Connection *connection,
final_contract_url,
session_id,
resource_url,
- h_contract_terms_str);
+ h_contract_terms_str,
+ mi);
GNUNET_free_non_null (h_contract_terms_str);
json_decref (contract_terms);
return ret;
@@ -370,7 +401,8 @@ MH_handler_check_payment (struct TMH_RequestHandler *rh,
final_contract_url,
session_id,
resource_url,
- h_contract_terms_str);
+ h_contract_terms_str,
+ mi);
GNUNET_free_non_null (h_contract_terms_str);
GNUNET_free (final_contract_url);
json_decref (contract_terms);
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c
index bf15106b..bf9b5440 100644
--- a/src/backend/taler-merchant-httpd_pay.c
+++ b/src/backend/taler-merchant-httpd_pay.c
@@ -371,6 +371,12 @@ struct PayContext
* Transaction ID given in @e root.
*/
char *order_id;
+
+ /**
+ * Fulfillment URL from @e contract_terms.
+ */
+ char *fulfillment_url;
+
};
@@ -610,6 +616,7 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
}
GNUNET_free_non_null (pc->order_id);
GNUNET_free_non_null (pc->session_id);
+ GNUNET_free_non_null (pc->fulfillment_url);
GNUNET_CONTAINER_DLL_remove (pc_head,
pc_tail,
pc);
@@ -1504,6 +1511,7 @@ parse_pay (struct MHD_Connection *connection,
pc->mi->id);
{
+ const char *fulfillment_url;
struct GNUNET_JSON_Specification espec[] = {
GNUNET_JSON_spec_absolute_time ("refund_deadline",
&pc->refund_deadline),
@@ -1515,6 +1523,8 @@ parse_pay (struct MHD_Connection *connection,
&pc->max_fee),
TALER_JSON_spec_amount ("amount",
&pc->amount),
+ GNUNET_JSON_spec_string ("fulfillment_url",
+ &fulfillment_url),
GNUNET_JSON_spec_fixed_auto ("H_wire",
&pc->h_wire),
GNUNET_JSON_spec_end()
@@ -1530,6 +1540,8 @@ parse_pay (struct MHD_Connection *connection,
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
+ pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
+
/* Use the value from config as default. */
used_wire_transfer_delay = wire_transfer_delay;
@@ -2021,11 +2033,40 @@ begin_transaction (struct PayContext *pc)
ec);
return;
}
- /* Payment succeeded, commit! */
+ /* Payment succeeded, save in database */
qs = db->mark_proposal_paid (db->cls,
&pc->h_contract_terms,
&pc->mi->pubkey,
pc->session_id);
+
+ if (qs < 0)
+ {
+ db->rollback (db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ begin_transaction (pc);
+ return;
+ }
+ resume_pay_with_error
+ (pc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+ "Merchant database error: could not "
+ "mark proposal as 'paid'");
+ return;
+ }
+
+ if ( (NULL != pc->session_id) && (NULL != pc->fulfillment_url) )
+ {
+ qs = db->insert_session_info (db->cls,
+ pc->session_id,
+ pc->fulfillment_url,
+ pc->order_id,
+ &pc->mi->pubkey);
+ }
+
+ /* Now commit! */
+
if (0 <= qs)
qs = db->commit (db->cls);
else
@@ -2045,6 +2086,8 @@ begin_transaction (struct PayContext *pc)
"mark proposal as 'paid'");
return;
}
+
+
resume_pay_with_response (pc,
MHD_HTTP_OK,
sign_success_response (pc));
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index 0d0f337d..1b4828e6 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -114,6 +114,7 @@ postgres_drop_tables (void *cls)
GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_tip_reserve_credits CASCADE;"),
GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_tip_reserves CASCADE;"),
GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_orders CASCADE;"),
+ GNUNET_PQ_make_try_execute ("DROP TABLE IF EXISTS merchant_session_info CASCADE;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
@@ -257,6 +258,16 @@ postgres_initialize (void *cls)
",amount_frac INT4 NOT NULL"
",PRIMARY KEY (pickup_id)"
");"),
+ /* sessions and their order_id/resource_url mapping */
+ GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS merchant_session_info ("
+ " session_id VARCHAR NOT NULL"
+ ",resource_url VARCHAR NOT NULL"
+ ",order_id VARCHAR NOT NULL"
+ ",merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)"
+ ",timestamp INT8 NOT NULL"
+ ",PRIMARY KEY (session_id, resource_url, merchant_pub)"
+ ",UNIQUE (session_id, resource_url, order_id, merchant_pub)"
+ ");"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
struct GNUNET_PQ_PreparedStatement ps[] = {
@@ -326,6 +337,16 @@ postgres_initialize (void *cls)
" VALUES "
"($1, $2, $3, $4)",
4),
+ GNUNET_PQ_make_prepare ("insert_session_info",
+ "INSERT INTO merchant_session_info"
+ "(session_id"
+ ",resource_url"
+ ",order_id"
+ ",merchant_pub"
+ ",timestamp)"
+ " VALUES "
+ "($1, $2, $3, $4, $5)",
+ 4),
GNUNET_PQ_make_prepare ("mark_proposal_paid",
"UPDATE merchant_contract_terms SET"
" paid=TRUE, last_session_id=$3"
@@ -413,6 +434,15 @@ postgres_initialize (void *cls)
" order_id=$1"
" AND merchant_pub=$2",
2),
+ GNUNET_PQ_make_prepare ("find_session_info",
+ "SELECT"
+ " order_id"
+ " FROM merchant_session_info"
+ " WHERE"
+ " resource_url=$1"
+ " AND session_id=$2"
+ " AND merchant_pub=$3",
+ 2),
GNUNET_PQ_make_prepare ("find_contract_terms_by_date",
"SELECT"
" contract_terms"
@@ -1111,6 +1141,82 @@ postgres_mark_proposal_paid (void *cls,
/**
+ * Store the order ID that was used to pay for a resource within a session.
+ *
+ * @param cls closure
+ * @param session_id session id
+ * @param resource_url URL that canonically identifies the resource
+ * being paid for
+ * @param order_id the order ID that was used when paying for the resource URL
+ * @param merchant_pub public key of the merchant, identifying the instance
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_insert_session_info (void *cls,
+ const char *session_id,
+ const char *resource_url,
+ const char *order_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub)
+{
+ struct PostgresClosure *pg = cls;
+
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_id),
+ GNUNET_PQ_query_param_auto_from_type (resource_url),
+ GNUNET_PQ_query_param_auto_from_type (order_id),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_session_info",
+ params);
+}
+
+/**
+ * Retrieve the order ID that was used to pay for a resource within a session.
+ *
+ * @param cls closure
+ * @param[out] order_id location to store the order ID that was used when
+ * paying for the resource URL
+ * @param session_id session id
+ * @param resource_url URL that canonically identifies the resource
+ * being paid for
+ * @param merchant_pub public key of the merchant, identifying the instance
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_find_session_info (void *cls,
+ char **order_id,
+ const char *session_id,
+ const char *resource_url,
+ const struct TALER_MerchantPublicKeyP *merchant_pub)
+{
+ struct PostgresClosure *pg = cls;
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (session_id),
+ GNUNET_PQ_query_param_auto_from_type (resource_url),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("order_id",
+ order_id),
+ GNUNET_PQ_result_spec_end
+ };
+ // We don't clean up the result spec since we want
+ // to keep around the memory for order_id.
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "find_contract_terms_history",
+ params,
+ rs);
+}
+
+
+/**
* Insert payment confirmation from the exchange into the database.
*
* @param cls closure
@@ -3559,6 +3665,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->lookup_wire_fee = &postgres_lookup_wire_fee;
plugin->increase_refund_for_contract_NT = &postgres_increase_refund_for_contract_NT;
plugin->mark_proposal_paid = &postgres_mark_proposal_paid;
+ plugin->insert_session_info = &postgres_insert_session_info;
+ plugin->find_session_info = &postgres_find_session_info;
plugin->enable_tip_reserve_TR = &postgres_enable_tip_reserve_TR;
plugin->authorize_tip_TR = &postgres_authorize_tip_TR;
plugin->lookup_tip_by_id = &postgres_lookup_tip_by_id;
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index f2dad94a..7d4948e1 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -250,6 +250,43 @@ struct TALER_MERCHANTDB_Plugin
const char *last_session_id);
/**
+ * Store the order ID that was used to pay for a resource within a session.
+ *
+ * @param cls closure
+ * @param session_id session id
+ * @param resource_url URL that canonically identifies the resource
+ * being paid for
+ * @param order_id the order ID that was used when paying for the resource URL
+ * @param merchant_pub public key of the merchant, identifying the instance
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_session_info) (void *cls,
+ const char *session_id,
+ const char *resource_url,
+ const char *order_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub);
+
+ /**
+ * Retrieve the order ID that was used to pay for a resource within a session.
+ *
+ * @param cls closure
+ * @param[out] order_id location to store the order ID that was used when
+ * paying for the resource URL
+ * @param session_id session id
+ * @param resource_url URL that canonically identifies the resource
+ * being paid for
+ * @param merchant_pub public key of the merchant, identifying the instance
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*find_session_info) (void *cls,
+ char **order_id,
+ const char *session_id,
+ const char *resource_url,
+ const struct TALER_MerchantPublicKeyP *merchant_pub);
+
+ /**
* Retrieve proposal data given its order ID.
*
* @param cls closure