commit 29dec4cf538daf22690386514387eb8a066b6872
parent e83e69a248ec1451e19bb233c6c2c5a825b67b66
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 24 Dec 2015 21:30:40 +0100
Merge branch 'master' of ssh://taler.net:/var/git/merchant
Diffstat:
15 files changed, 240 insertions(+), 66 deletions(-)
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf
@@ -25,7 +25,7 @@ EDATE = 3 week
DB = postgres
[mint-taler]
-URI = mint.demo.taler.net
+URI = http://mint.demo.taler.net/
MASTER_KEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0
# Auditors must be in sections "auditor-", the rest of the section
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
@@ -103,13 +103,13 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
&contract.purpose,
&contract_sig);
- pay_url = json_object_get (root, "pay_url");
+ pay_url = json_object_get (jcontract, "pay_url");
if (NULL == pay_url)
{
return TMH_RESPONSE_reply_internal_error (connection,
"pay url missing");
}
- exec_url = json_object_get (root, "exec_url");
+ exec_url = json_object_get (jcontract, "exec_url");
if (NULL == exec_url)
{
return TMH_RESPONSE_reply_internal_error (connection,
diff --git a/src/backend/taler-merchant-httpd_mints.c b/src/backend/taler-merchant-httpd_mints.c
@@ -230,6 +230,8 @@ context_task (void *cls,
struct GNUNET_NETWORK_FDSet *ws;
struct GNUNET_TIME_Relative delay;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "In mint context polling task\n");
+
poller_task = NULL;
TALER_MINT_perform (ctx);
max_fd = -1;
@@ -243,6 +245,9 @@ context_task (void *cls,
&except_fd_set,
&max_fd,
&timeout);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "In mint context polling task, max_fd=%d, timeout=%ld\n",
+ max_fd, timeout);
if (timeout >= 0)
delay =
GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
@@ -319,6 +324,11 @@ TMH_MINTS_find_mint (const char *chosen_mint,
GNUNET_break (0);
return NULL;
}
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Trying to find chosen mint `%s'\n",
+ chosen_mint);
+
/* Check if the mint is known */
for (mint = mint_head; NULL != mint; mint = mint->next)
/* test it by checking public key --- FIXME: hostname or public key!?
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c
@@ -710,6 +710,10 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
}
}
+ /* Check if this payment attempt has already taken place */
+ if (GNUNET_OK == db->check_payment (db->cls, pc->transaction_id))
+ return TMH_RESPONSE_reply_external_error (connection, "payment already attempted");
+
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Looking up chosen mint '%s'\n", pc->chosen_mint);
/* Find the responsible mint, this may take a while... */
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
@@ -70,7 +70,7 @@ postgres_initialize (void *cls,
"CREATE %1$s TABLE IF NOT EXISTS payments ("
"h_contract BYTEA NOT NULL,"
"h_wire BYTEA NOT NULL,"
- "transaction_id INT8 PRIMARY KEY,"
+ "transaction_id INT8," /*WARNING: this column used to be primary key, but that wrong since multiple coins belong to the same id*/
"timestamp INT8 NOT NULL,"
"refund_deadline INT8 NOT NULL,"
"amount_without_fee_val INT8 NOT NULL,"
@@ -108,6 +108,22 @@ postgres_initialize (void *cls,
}
return GNUNET_SYSERR;
}
+ if ( (NULL == (res = PQprepare (pg->conn,
+ "check_payment",
+ "SELECT * "
+ "FROM payments "
+ "WHERE transaction_id=$1",
+ 1, NULL))) ||
+ (PGRES_COMMAND_OK != (status = PQresultStatus(res))) )
+ {
+ if (NULL != res)
+ {
+ PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res);
+ PQclear (res);
+ }
+ return GNUNET_SYSERR;
+ }
+
PQclear (res);
return GNUNET_OK;
}
@@ -190,7 +206,61 @@ postgres_store_payment (void *cls,
return GNUNET_OK;
}
+/**
+ * Check whether a payment has already been stored
+ *
+ * @param cls our plugin handle
+ * @param transaction_id the transaction id to search into
+ * the db
+ *
+ * @return GNUNET_OK if found, GNUNET_NO if not, GNUNET_SYSERR
+ * upon error
+ */
+static int
+postgres_check_payment(void *cls,
+ uint64_t transaction_id)
+{
+ struct PostgresClosure *pg = cls;
+ PGresult *res;
+ ExecStatusType status;
+ struct TALER_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_uint64 (&transaction_id),
+ TALER_PQ_query_param_end
+ };
+ res = TALER_PQ_exec_prepared (pg->conn,
+ "check_payment",
+ params);
+
+ status = PQresultStatus (res);
+ if (PGRES_TUPLES_OK != status)
+ {
+ const char *sqlstate;
+
+ sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE);
+ if (NULL == sqlstate)
+ {
+ /* very unexpected... */
+ GNUNET_break (0);
+ PQclear (res);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not check if contract %llu is in DB: %s\n",
+ transaction_id,
+ sqlstate);
+ PQclear (res);
+ return GNUNET_SYSERR;
+ }
+ /* count rows */
+ if (PQntuples (res) > 0)
+ return GNUNET_OK;
+ return GNUNET_NO;
+
+
+
+}
/**
* Initialize Postgres database subsystem.
*
@@ -220,6 +290,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->cls = pg;
plugin->initialize = &postgres_initialize;
plugin->store_payment = &postgres_store_payment;
+ plugin->check_payment = &postgres_check_payment;
return plugin;
}
diff --git a/src/frontend/checkout.php b/src/frontend/checkout.php
@@ -55,6 +55,7 @@
// create PHP session and store donation information in session
$donation_fraction = (float) ("0." . $donation_fraction);
session_start();
+ session_unset();
$_SESSION['receiver'] = $donation_receiver;
$_SESSION['amount_value'] = (int) $donation_amount;
$_SESSION['amount_fraction'] = (int) ($donation_fraction * 1000000);
@@ -64,8 +65,8 @@
<header>
<div id="logo">
<svg height="100" width="100">
- <circle cx="50" cy="50" r="40" stroke="black" stroke-width="6" fill="white" />
- <text x="19" y="82" font-family="Verdana" font-size="90" fill="black">S</text>
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
</svg>
</div>
diff --git a/src/frontend/execute.js b/src/frontend/execute.js
@@ -0,0 +1,32 @@
+"use strict";
+// JSX literals are compiled to calls to React.createElement calls.
+let React = {
+ createElement: function (tag, props, ...children) {
+ let e = document.createElement(tag);
+ for (let k in props) {
+ e.setAttribute(k, props[k]);
+ }
+ for (let child of children) {
+ if ("string" === typeof child || "number" == typeof child) {
+ child = document.createTextNode(child);
+ }
+ e.appendChild(child);
+ }
+ return e;
+ }
+};
+document.addEventListener("DOMContentLoaded", function (e) {
+ var eve = new CustomEvent('taler-execute-payment', { detail: { H_contract: h_contract } });
+ document.dispatchEvent(eve);
+});
+function replace(el, r) {
+ el.parentNode.replaceChild(r, el);
+}
+document.addEventListener("taler-payment-result", function (e) {
+ if (!e.detail.success) {
+ alert("Payment failed\n" + JSON.stringify(e.detail));
+ }
+ console.log("finished payment");
+ let msg = React.createElement("div", null, "Payment successful. View your ", React.createElement("a", {"href": e.detail.fulfillmentUrl}, "product"), ".");
+ replace(document.getElementById("loading"), msg);
+});
diff --git a/src/frontend/execute.php b/src/frontend/execute.php
@@ -31,26 +31,16 @@
session_start();
echo "var h_contract=\"$_SESSION[H_contract]\";\n";
?>
-document.addEventListener("DOMContentLoaded", function (e) {
- var eve = new CustomEvent('taler-execute-payment', {detail: {H_contract: h_contract}});
- document.dispatchEvent(eve);
-});
-document.addEventListener("taler-payment-result", function (e) {
- if (!e.detail.success) {
- alert("Payment failed\n" + JSON.strinfigy(e.detail));
- }
- console.log("finished payment");
- document.getElementById("loading").innerHTML = "success!";
-});
</script>
+ <script type="text/javascript" src="execute.js"></script>
</head>
<body>
<header>
<div id="logo">
<svg height="100" width="100">
- <circle cx="50" cy="50" r="40" stroke="black" stroke-width="6" fill="white" />
- <text x="19" y="82" font-family="Verdana" font-size="90" fill="black">S</text>
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
</svg>
</div>
<h1>Toy Store - Taler Demo</h1>
diff --git a/src/frontend/execute.tsx b/src/frontend/execute.tsx
@@ -0,0 +1,40 @@
+"use strict";
+
+
+// JSX literals are compiled to calls to React.createElement calls.
+let React = {
+ createElement: function(tag, props, ...children) {
+ let e = document.createElement(tag);
+ for (let k in props) {
+ e.setAttribute(k, props[k]);
+ }
+ for (let child of children) {
+ if ("string" === typeof child || "number" == typeof child) {
+ child = document.createTextNode(child);
+ }
+ e.appendChild(child);
+ }
+ return e;
+ }
+};
+
+declare var h_contract: string;
+
+document.addEventListener("DOMContentLoaded", function (e) {
+ var eve = new CustomEvent('taler-execute-payment', {detail: {H_contract: h_contract}});
+ document.dispatchEvent(eve);
+});
+
+function replace(el, r) {
+ el.parentNode.replaceChild(r, el);
+}
+
+document.addEventListener("taler-payment-result", function (e: CustomEvent) {
+ if (!e.detail.success) {
+ alert("Payment failed\n" + JSON.stringify(e.detail));
+ }
+ console.log("finished payment");
+ let msg =
+ <div>Payment successful. View your <a href={e.detail.fulfillmentUrl}>product</a>.</div>;
+ replace(document.getElementById("loading"), msg);
+});
diff --git a/src/frontend/fulfillment.php b/src/frontend/fulfillment.php
@@ -9,8 +9,8 @@
<header>
<div id="logo">
<svg height="100" width="100">
- <circle cx="50" cy="50" r="40" stroke="black" stroke-width="6" fill="white" />
- <text x="19" y="82" font-family="Verdana" font-size="90" fill="black">S</text>
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
</svg>
</div>
diff --git a/src/frontend/generate_taler_contract.php b/src/frontend/generate_taler_contract.php
@@ -30,18 +30,7 @@
if the whole "journey" to the backend is begin tested
- $ curl http://merchant_url/generate_taler_contract.php?backend_test=no
if just the frontend job is being tested
- */
-
-
-register_shutdown_function(function() {
- $lastError = error_get_last();
-
- if (!empty($lastError) && $lastError['type'] == E_ERROR) {
- header('Status: 500 Internal Server Error');
- header('HTTP/1.0 500 Internal Server Error');
- }
-});
-
+*/
$cli_debug = false;
$backend_test = true;
@@ -61,6 +50,7 @@ if (!$cli_debug && (! isset($_SESSION['receiver'])))
{
http_response_code (404);
echo "Please select a contract before getting to this page...";
+ echo "attempted : " . $_SESSION['receiver'];
exit (0);
}
@@ -100,6 +90,9 @@ $teatax = array ('value' => 1,
// Take a timestamp
$now = new DateTime('now');
+$PAY_URL = "pay.php";
+$EXEC_URL = "execute.php";
+
// pack the JSON for the contract
// --- FIXME: exact format needs review!
$contract = array ('amount' => array ('value' => $amount_value,
@@ -120,6 +113,8 @@ $contract = array ('amount' => array ('value' => $amount_value,
'delivery_date' => "Some Date Format",
'delivery_location' => 'LNAME1')),
'timestamp' => "/Date(" . $now->getTimestamp() . ")/",
+ 'pay_url' => $PAY_URL,
+ 'exec_url' => $EXEC_URL,
'expiry' => "/Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")/",
'refund_deadline' => "/Date(" . $now->add(new DateInterval('P3M'))->getTimestamp() . ")/",
'merchant' => array ('address' => 'LNAME2',
@@ -147,9 +142,8 @@ $contract = array ('amount' => array ('value' => $amount_value,
'state' => 'Test State',
'region' => 'Test Region',
'province' => 'Test Province',
- 'ZIP code' => 4908)));
-
-$json = json_encode (array ("contract" => $contract, "pay_url" => "pay.php", "exec_url" => "execute.php"));
+ 'ZIP code' => 4908)));
+$json = json_encode (array ('contract' => $contract, 'exec_url' => $EXEC_URL, 'pay_url' => $PAY_URL), JSON_PRETTY_PRINT);
if ($cli_debug && !$backend_test)
{
echo $json . "\n";
@@ -157,14 +151,13 @@ if ($cli_debug && !$backend_test)
}
-
-// Backend is relative to the shop site.
-$url = (new http\URL("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"))
+$url = (new http\URL("http://".$_SERVER["HTTP_HOST"]))
->mod(array ("path" => "backend/contract"), http\Url::JOIN_PATH);
-$req = new http\Client\Request ("POST",
- $url,
- array ("Content-Type" => "application/json"));
+$req = new http\Client\Request("POST",
+ $url,
+ array ("Content-Type" => "application/json"));
+
$req->getBody()->append ($json);
// Execute the HTTP request
@@ -181,15 +174,12 @@ http_response_code ($status_code);
// Now generate our body
if ($status_code != 200)
{
- echo "Error while generating the contract:";
- echo "$resp";
+ echo "Error while generating the contract";
+ echo $resp->body->toString ();
}
else
-{
-
- $json = json_decode($resp->body->toString (), true);
- $_SESSION["H_contract"] = $json["H_contract"];
- // send the contract back to the wallet without touching it
+{ $got_json = json_decode ($resp->body->toString ());
+ $_SESSION['H_contract'] = $got_json->H_contract;
echo $resp->body->toString ();
}
?>
diff --git a/src/frontend/index.html b/src/frontend/index.html
@@ -32,8 +32,8 @@
<header>
<div id="logo">
<svg height="100" width="100">
- <circle cx="50" cy="50" r="40" stroke="black" stroke-width="6" fill="white" />
- <text x="19" y="82" font-family="Verdana" font-size="90" fill="black">S</text>
+ <circle cx="50" cy="50" r="40" stroke="darkcyan" stroke-width="6" fill="white" />
+ <text x="19" y="82" font-family="Verdana" font-size="90" fill="darkcyan">S</text>
</svg>
</div>
@@ -61,8 +61,6 @@
<tt>mint.demo.taler.net</tt> and
<tt>bank.demo.taler.net</tt>, correspond to a Taler mint
and bank with tight Taler integration respectively.
- <!-- TODO: maybe offer the wallet at 'taler.net/extension' in the
- future, instead of at 'demo.taler.net/'? -->
</p>
</article>
@@ -70,14 +68,8 @@
<article id="installation">
<h2>Step 1: Installing the Taler wallet <sup>(once)</sup></h2>
- <p>First, you need to install the Taler wallet browser extension.
- It is currently only available for Firefox. If you run
- Firefox, simply click <a href="http://demo.taler.net/extension">here</a>
- to download and install the extension. You will then have to
- click on "allow" and "install" dialogs shown by Firefox.
- After that, the Taler logo should appear on the right side
- of your navigation bar.
- <!-- TODO: insert screenshot highlighting the icon? -->
+ <p>First, you need to <a href="http://demo.taler.net/">install</a>
+ the Taler wallet browser extension.
</p>
</article>
diff --git a/src/frontend/pay.php b/src/frontend/pay.php
@@ -41,8 +41,6 @@ if (isset($_GET['backend_test']) && $_GET['backend_test'] == 'no')
$backend_test = false;
}
-
-
if (!isset($_SESSION['H_contract']))
{
echo "No session active.";
@@ -50,6 +48,17 @@ if (!isset($_SESSION['H_contract']))
return;
}
+if (isset($_SESSION['payment_ok']) && $_SESSION['payment_ok'] == true)
+{
+ $_SESSION['payment_ok'] = true;
+ http_response_code (301);
+ //$url = (new http\URL("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"))
+ $url = (new http\URL("http://$_SERVER[HTTP_HOST]"))
+ ->mod(array ("path" => "fulfillment.php"), http\Url::JOIN_PATH);
+ header("Location: $url");
+ die();
+}
+
$post_body = file_get_contents('php://input');
$now = new DateTime('now');
@@ -84,7 +93,13 @@ if ($cli_debug && !$backend_test)
// Backend is relative to the shop site.
-$url = (new http\URL("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"))
+/**
+ * WARNING: the "shop site" is '"http://".$_SERVER["HTTP_HOST"]'
+ * So do not attach $_SERVER["REQUEST_URI"] before proxying requests
+ * to the backend
+ */
+//$url = (new http\URL("http://".$_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"]))
+$url = (new http\URL("http://".$_SERVER["HTTP_HOST"]))
->mod(array ("path" => "backend/pay"), http\Url::JOIN_PATH);
$req = new http\Client\Request("POST",
@@ -118,7 +133,13 @@ else
{
$_SESSION['payment_ok'] = true;
http_response_code (301);
- $url = (new http\URL("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"))
+ /**
+ * WARNING: the "shop site" is '"http://".$_SERVER["HTTP_HOST"]'
+ * So do not attach $_SERVER["REQUEST_URI"] before proxying requests
+ * to the backend
+ */
+ //$url = (new http\URL("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"))
+ $url = (new http\URL("http://$_SERVER[HTTP_HOST]"))
->mod(array ("path" => "fulfillment.php"), http\Url::JOIN_PATH);
header("Location: $url");
die();
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -83,6 +83,19 @@ struct TALER_MERCHANTDB_Plugin
const struct TALER_CoinSpendPublicKeyP *coin_pub,
json_t *mint_proof);
-};
+ /**
+ * Check whether a payment has already been stored
+ *
+ * @param cls our plugin handle
+ * @param transaction_id the transaction id to search into
+ * the db
+ *
+ * @return GNUNET_OK if found, GNUNET_NO if not, GNUNET_SYSERR
+ * upon error
+ */
+ int
+ (*check_payment) (void *cls,
+ uint64_t transaction_id);
+};
#endif
diff --git a/src/tsconfig.json b/src/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "jsx": "react"
+ },
+ "files": [
+ "frontend/execute.tsx"
+ ]
+}
+