summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2015-12-23 00:00:17 +0100
committerFlorian Dold <florian.dold@gmail.com>2015-12-23 00:00:17 +0100
commit96c3b85c6f12f2d0330180e5953ac8a1674c2731 (patch)
treec1d67f01454664a8395833ed220e19ca6e2cb46d
parentcae3b8219845b15d890c24f46161f69f534200c3 (diff)
parent65cef9a1b4d958b59d93893ac3652c1a956e11e3 (diff)
downloadmerchant-96c3b85c6f12f2d0330180e5953ac8a1674c2731.tar.gz
merchant-96c3b85c6f12f2d0330180e5953ac8a1674c2731.tar.bz2
merchant-96c3b85c6f12f2d0330180e5953ac8a1674c2731.zip
Merge branch 'master' of ssh://taler.net/var/git/merchant
-rw-r--r--.gitignore4
-rw-r--r--contrib/merchant.conf14
-rw-r--r--src/backend/merchant.conf4
-rw-r--r--src/backend/taler-merchant-httpd.c21
-rw-r--r--src/backend/taler-merchant-httpd_contract.c41
-rw-r--r--src/backend/taler-merchant-httpd_pay.c17
-rw-r--r--src/frontend/checkout.php14
-rw-r--r--src/frontend/execute.php66
-rw-r--r--src/frontend/fulfillment.php (renamed from src/frontend/fullfillment.php)4
-rw-r--r--src/frontend/generate_taler_contract.php97
-rw-r--r--src/frontend/index.html18
-rw-r--r--src/frontend/pay.php46
-rw-r--r--src/frontend/style.css58
-rw-r--r--src/include/taler_merchant_service.h16
-rw-r--r--src/lib/Makefile.am6
-rw-r--r--src/lib/merchant_api_context.c20
-rw-r--r--src/lib/merchant_api_pay.c292
-rw-r--r--src/lib/test-mint-home/config/mint-common.conf30
-rw-r--r--src/lib/test-mint-home/config/mint-keyup.conf86
-rw-r--r--src/lib/test-mint-home/master.priv1
-rw-r--r--src/lib/test-mint-home/sepa.json6
-rw-r--r--src/lib/test_merchant.conf55
-rw-r--r--src/lib/test_merchant.priv1
-rw-r--r--src/lib/test_merchant_api.c201
24 files changed, 870 insertions, 248 deletions
diff --git a/.gitignore b/.gitignore
index 618e423f..84d6bd6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,7 @@ GPATH
GRTAGS
GTAGS
*.swp
+src/backend/taler-merchant-httpd
+src/lib/test_merchant_api
+taler_merchant_config.h
+taler_merchant_config.h.in
diff --git a/contrib/merchant.conf b/contrib/merchant.conf
deleted file mode 100644
index e1727b4f..00000000
--- a/contrib/merchant.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-[merchant]
-PORT = 4251
-
-# List of mints the merchant trusts delimited by a single space
-TRUSTED_MINTS = taler
-
-[mint-taler]
-HOSTNAME = taler.org
-PORT = 4241
-# The public key of this mint
-PUBKEY = ...
-
-[merchant-db]
-CONFIG = postgres:///taler \ No newline at end of file
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf
index 80a7b412..93d9fbba 100644
--- 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
@@ -51,3 +51,5 @@ CONFIG = postgres:///talerdemo
IBAN = DE67830654080004822650
NAME = GNUNET E.V
BIC = GENODEF1SRL
+ADDRESS = "Garching"
+SALT = 12345
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 2cc553e9..21fd545a 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -334,6 +334,7 @@ parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg)
unsigned long long salt;
char *iban;
char *name;
+ char *address;
char *bic;
if (GNUNET_OK !=
@@ -366,21 +367,35 @@ parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg)
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
"wire-sepa",
+ "ADDRESS",
+ &address))
+ {
+ GNUNET_free (iban);
+ GNUNET_free (name);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "wire-sepa",
"BIC",
&bic))
{
GNUNET_free (iban);
GNUNET_free (name);
GNUNET_free (bic);
+ GNUNET_free (address);
+ return GNUNET_SYSERR;
}
- j_wire = json_pack ("{s:s, s:s, s:s, s:s, s:o}",
+ j_wire = json_pack ("{s:s, s:s, s:s, s:s, s:o, s:s}",
"type", "SEPA",
"IBAN", iban,
"name", name,
"bic", bic,
- "r", json_integer (salt));
+ "r", json_integer (salt),
+ "address", address);
GNUNET_free (iban);
GNUNET_free (name);
+ GNUNET_free (address);
GNUNET_free (bic);
if (NULL == j_wire)
return GNUNET_SYSERR;
@@ -491,7 +506,7 @@ run (void *cls,
TMH_AUDITORS_init (config));
/* FIXME: for now, we just support SEPA here: */
EXITIF (GNUNET_OK !=
- parse_wireformat_sepa (config));
+ parse_wireformat_sepa (config));
EXITIF (GNUNET_OK !=
validate_and_hash_wireformat ("SEPA"));
EXITIF (GNUNET_OK !=
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
index e44d3b84..b351c620 100644
--- a/src/backend/taler-merchant-httpd_contract.c
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -52,6 +52,9 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
size_t *upload_data_size)
{
json_t *root;
+ json_t *jcontract;
+ json_t *pay_url;
+ json_t *exec_url;
int res;
struct TALER_ContractPS contract;
struct GNUNET_CRYPTO_EddsaSignature contract_sig;
@@ -67,18 +70,26 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
if ((GNUNET_NO == res) || (NULL == root))
return MHD_YES;
- /* add fields to the "root" that the backend should provide */
- json_object_set (root,
+ jcontract = json_object_get (root, "contract");
+
+ if (NULL == jcontract)
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ "contract request malformed");
+ }
+
+ /* add fields to the contract that the backend should provide */
+ json_object_set (jcontract,
"mints",
trusted_mints);
- json_object_set (root,
+ json_object_set (jcontract,
"auditors",
j_auditors);
- json_object_set_new (root,
+ json_object_set_new (jcontract,
"H_wire",
TALER_json_from_data (&h_wire,
sizeof (h_wire)));
- json_object_set_new (root,
+ json_object_set_new (jcontract,
"merchant_pub",
TALER_json_from_data (&pubkey,
sizeof (pubkey)));
@@ -91,11 +102,27 @@ MH_handler_contract (struct TMH_RequestHandler *rh,
GNUNET_CRYPTO_eddsa_sign (privkey,
&contract.purpose,
&contract_sig);
+
+ pay_url = json_object_get (root, "pay_url");
+ if (NULL == pay_url)
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ "pay url missing");
+ }
+ exec_url = json_object_get (root, "exec_url");
+ if (NULL == exec_url)
+ {
+ return TMH_RESPONSE_reply_internal_error (connection,
+ "exec url missing");
+ }
+
/* return final response */
return TMH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_OK,
- "{s:o, s:o, s:o}",
- "contract", root,
+ "{s:o, s:o, s:o, s:o, s:o}",
+ "contract", jcontract,
+ "exec_url", exec_url,
+ "pay_url", pay_url,
"sig", TALER_json_from_data (&contract_sig,
sizeof (contract_sig)),
"H_contract", TALER_json_from_data (&contract.h_contract,
diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c
index 319a7001..10f55d4b 100644
--- a/src/backend/taler-merchant-httpd_pay.c
+++ b/src/backend/taler-merchant-httpd_pay.c
@@ -543,11 +543,12 @@ process_pay_with_mint (void *cls,
dc);
if (NULL == dc->dh)
{
+ /* Signature was invalid. If the mint was unavailable,
+ * we'd get that information in the callback. */
resume_pay_with_response (pc,
- MHD_HTTP_SERVICE_UNAVAILABLE,
- TMH_RESPONSE_make_json_pack ("{s:s, s:i}",
- "mint", pc->chosen_mint,
- "transaction_id", pc->transaction_id));
+ MHD_HTTP_UNAUTHORIZED,
+ TMH_RESPONSE_make_json_pack ("{s:s}",
+ "hint", "Coin signature invalid."));
return;
}
}
@@ -577,6 +578,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
int res;
json_t *root;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "In handler for /pay.\n");
+
if (NULL == *connection_cls)
{
pc = GNUNET_new (struct PayContext);
@@ -594,6 +597,7 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
/* We are *done* processing the request, just queue the response (!) */
if (UINT_MAX == pc->response_code)
return MHD_NO; /* hard error */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Queueing response for /pay.\n");
res = MHD_queue_response (connection,
pc->response_code,
pc->response);
@@ -635,12 +639,15 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
res = TMH_PARSE_json_data (connection,
root,
spec);
+
if (GNUNET_YES != res)
{
json_decref (root);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Parsed JSON for /pay.\n");
+
/* 'edate' is optional, if it is not present, generate it here; it
will be timestamp plus the edate_delay supplied in config
file */
@@ -703,6 +710,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
}
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Looking up chosen mint '%s'\n", pc->chosen_mint);
+
/* Find the responsible mint, this may take a while... */
pc->pending = pc->coins_cnt;
pc->fo = TMH_MINTS_find_mint (pc->chosen_mint,
diff --git a/src/frontend/checkout.php b/src/frontend/checkout.php
index c002f52a..e4dd8ebd 100644
--- a/src/frontend/checkout.php
+++ b/src/frontend/checkout.php
@@ -51,7 +51,7 @@
$donation_currency = $_POST['donation_currency'];
// get frational part
- list ($donation_value, $donation_fraction) = split ("\.", $donation_amount, 2);
+ list ($donation_value, $donation_fraction) = explode (".", $donation_amount, 2);
// create PHP session and store donation information in session
$donation_fraction = (float) ("0." . $donation_fraction);
session_start();
@@ -64,8 +64,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>
@@ -116,7 +116,7 @@
<input type="radio" name="payment_system" value="taler"
id="taler-radio-button-id" disabled="true">Taler</input>
<br/>
- <input type="button" onclick="pay(this.form)" value="Ok">
+ <input type="button" onclick="pay(this.form)" value="Ok"></input>
</div>
</form>
@@ -147,7 +147,7 @@ function taler_pay(form)
have its own way of generating and transmitting the
contract, there just must be a way to get the contract
and to pass it to the wallet when the user selects 'Pay'. */
- contract_request.open("GET", "/generate_taler_contract.php", true);
+ contract_request.open("GET", "generate_taler_contract.php", true);
contract_request.onload = function (e)
{
if (contract_request.readyState == 4)
@@ -155,9 +155,9 @@ function taler_pay(form)
if (contract_request.status == 200)
{
/* display contract_requestificate (i.e. it sends the JSON string
- to the extension) alert (contract_request.responseText); */
+ to the extension) alert (contract_request.responseText); */
+ console.log("response text:", contract_request.responseText);
handle_contract(contract_request.responseText);
-
}
else
{
diff --git a/src/frontend/execute.php b/src/frontend/execute.php
new file mode 100644
index 00000000..61c8e197
--- /dev/null
+++ b/src/frontend/execute.php
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Toy Store - Taler Demo</title>
+ <link rel="stylesheet" type="text/css" href="style.css">
+ <script> /* @licstart The following is the entire license notice for the
+ JavaScript code in this page.
+
+ Copyright (C) 2015 GNUnet e.V.
+
+ The JavaScript code in this page is free software: you can
+ redistribute it and/or modify it under the terms of the GNU
+ Lesser General Public License (GNU LGPL) as published by the Free Software
+ Foundation, either version 2.1 of the License, or (at your option)
+ any later version. The code is distributed WITHOUT ANY WARRANTY;
+ without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU LGPL for more details.
+
+ As additional permission under GNU LGPL version 2.1 section 7, you
+ may distribute non-source (e.g., minimized or compacted) forms of
+ that code without the copy of the GNU LGPL normally required by
+ section 4, provided you include this license notice and a URL
+ through which recipients can access the Corresponding Source.
+
+ @licend The above is the entire license notice
+ for the JavaScript code in this page.
+ */
+ </script>
+ <script type="text/javascript">
+<?php
+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>
+</head>
+
+<body>
+ <header>
+ <div id="logo">
+ <svg height="100" width="100">
+ <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>
+ </header>
+
+ <aside class="sidebar" id="left">
+ </aside>
+
+ <section id="main">
+ <h1>Executing Payment ...</h1>
+ <div id="loading">Loading...</div>
+</body>
+</html>
diff --git a/src/frontend/fullfillment.php b/src/frontend/fulfillment.php
index f22fd1d8..32f3c0cd 100644
--- a/src/frontend/fullfillment.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
index 1e020ce7..c71542c8 100644
--- a/src/frontend/generate_taler_contract.php
+++ b/src/frontend/generate_taler_contract.php
@@ -90,58 +90,55 @@ $teatax = array ('value' => 1,
// Take a timestamp
$now = new DateTime('now');
-// JSON for the contract
+// pack the JSON for the contract
// --- FIXME: exact format needs review!
-$contract = array ('amount' =>
- array ('value' => $amount_value,
- 'fraction' => $amount_fraction,
- 'currency' => $currency),
- 'max_fee' => array ('value' => 3,
- 'fraction' => 01010,
- 'currency' => $currency),
- 'transaction_id' => $transaction_id,
- 'products' => array (
- array ('description' => $desc,
- 'quantity' => 1,
- 'price' => array ('value' => $amount_value,
- 'fraction' => $amount_fraction,
+$json = json_encode (array ('amount' => array ('value' => $amount_value,
+ 'fraction' => $amount_fraction,
'currency' => $currency),
- 'product_id' => $p_id,
- 'taxes' => array (array ('teatax' => $teatax)),
- 'delivery_date' => "Some Date Format",
- 'delivery_location' => 'LNAME1')),
- 'timestamp' => "/Date(" . $now->getTimestamp() . ")/",
- 'expiry' => "/Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")/",
- 'refund_deadline' => "/Date(" . $now->add(new DateInterval('P3M'))->getTimestamp() . ")/",
- 'merchant' => array ('address' => 'LNAME2',
- 'name' => 'test merchant',
- 'jurisdiction' => 'LNAME3'),
-
- 'locations' => array ('LNAME1' => array ('country' => 'Test Country',
- 'city' => 'Test City',
- 'state' => 'Test State',
- 'region' => 'Test Region',
- 'province' => 'Test Province',
- 'ZIP code' => 4908,
- 'street' => 'test street',
- 'street number' => 20),
- 'LNAME2' => array ('country' => 'Test Country',
- 'city' => 'Test City',
- 'state' => 'Test State',
- 'region' => 'Test Region',
- 'province' => 'Test Province',
- 'ZIP code' => 4908,
- 'street' => 'test street',
- 'street number' => 20),
- 'LNAME3' => array ('country' => 'Test Country',
- 'city' => 'Test City',
- 'state' => 'Test State',
- 'region' => 'Test Region',
- 'province' => 'Test Province',
- 'ZIP code' => 4908)));
-$json = json_encode (array ('contract' => $contract,
- 'pay_url' => "/taler/pay",
- 'exec_url' => "exec"));
+ 'max_fee' => array ('value' => 3,
+ 'fraction' => 01010,
+ 'currency' => $currency),
+ 'transaction_id' => $transaction_id,
+ 'products' => array (
+ array ('description' => $desc,
+ 'quantity' => 1,
+ 'price' => array ('value' => $amount_value,
+ 'fraction' => $amount_fraction,
+ 'currency' => $currency),
+ 'product_id' => $p_id,
+ 'taxes' => array (array ('teatax' => $teatax)),
+ 'delivery_date' => "Some Date Format",
+ 'delivery_location' => 'LNAME1')),
+ 'timestamp' => "/Date(" . $now->getTimestamp() . ")/",
+ 'pay_url' => "/taler/pay",
+ 'expiry' => "/Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")/",
+ 'refund_deadline' => "/Date(" . $now->add(new DateInterval('P3M'))->getTimestamp() . ")/",
+ 'merchant' => array ('address' => 'LNAME2',
+ 'name' => 'test merchant',
+ 'jurisdiction' => 'LNAME3'),
+
+ 'locations' => array ('LNAME1' => array ('country' => 'Test Country',
+ 'city' => 'Test City',
+ 'state' => 'Test State',
+ 'region' => 'Test Region',
+ 'province' => 'Test Province',
+ 'ZIP code' => 4908,
+ 'street' => 'test street',
+ 'street number' => 20),
+ 'LNAME2' => array ('country' => 'Test Country',
+ 'city' => 'Test City',
+ 'state' => 'Test State',
+ 'region' => 'Test Region',
+ 'province' => 'Test Province',
+ 'ZIP code' => 4908,
+ 'street' => 'test street',
+ 'street number' => 20),
+ 'LNAME3' => array ('country' => 'Test Country',
+ 'city' => 'Test City',
+ 'state' => 'Test State',
+ 'region' => 'Test Region',
+ 'province' => 'Test Province',
+ 'ZIP code' => 4908))), JSON_PRETTY_PRINT);
if ($cli_debug && !$backend_test)
{
echo $json . "\n";
diff --git a/src/frontend/index.html b/src/frontend/index.html
index fb7ba64a..c67f8a58 100644
--- 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>
@@ -129,7 +121,7 @@
<p>So, please choose a project and the amount of KUDOS you
wish to donate:</p>
- <form name="tform" action="/checkout.php" method="POST">
+ <form name="tform" action="checkout.php" method="POST">
<div class="participation" id="fake-shop">
<br>
<input type="radio" name="donation_receiver" value="Taler" checked="true">GNU Taler</input>
diff --git a/src/frontend/pay.php b/src/frontend/pay.php
index aaa5d2e2..0bd4e3bb 100644
--- a/src/frontend/pay.php
+++ b/src/frontend/pay.php
@@ -23,7 +23,9 @@
NOTE: 'max_fee' must be consistent with the same value indicated within
the contract; thus, a "real" merchant must implement such a mapping
-*/
+ */
+
+session_start();
$cli_debug = false;
$backend_test = true;
@@ -39,7 +41,14 @@ if (isset($_GET['backend_test']) && $_GET['backend_test'] == 'no')
$backend_test = false;
}
-session_start();
+
+
+if (!isset($_SESSION['H_contract']))
+{
+ echo "No session active.";
+ http_response_code (301);
+ return;
+}
$post_body = file_get_contents('php://input');
@@ -49,15 +58,15 @@ $edate = array ('edate' =>
$deposit_permission = json_decode ($post_body, true);
-$to_add = array ('max_fee' => array ('value' => 3,
- 'fraction' => 8,
- 'currency' => $_SESSION['currency']),
- 'amount' => array ('value' => $_SESSION['amount_value'],
- 'fraction' => $_SESSION['amount_fraction'],
- 'currency' => $_SESSION['currency']));
+$to_add = array('max_fee' => array('value' => 3,
+ 'fraction' => 8,
+ 'currency' => $_SESSION['currency']),
+ 'amount' => array('value' => $_SESSION['amount_value'],
+ 'fraction' => $_SESSION['amount_fraction'],
+ 'currency' => $_SESSION['currency']));
-$new_deposit_permission = array_merge ($deposit_permission, $to_add);
-$new_deposit_permission_edate = array_merge ($new_deposit_permission, $edate);
+$new_deposit_permission = array_merge($deposit_permission, $to_add);
+$new_deposit_permission_edate = array_merge($new_deposit_permission, $edate);
/* Craft the HTTP request, note that the backend
could be on an entirely different machine if
@@ -68,14 +77,19 @@ if ($cli_debug && !$backend_test)
/* DO NOTE the newline at the end of 'echo's argument */
//echo json_encode ($new_deposit_permission_edate, JSON_PRETTY_PRINT)
- echo json_encode ($new_deposit_permission, JSON_PRETTY_PRINT)
+ echo json_encode($new_deposit_permission, JSON_PRETTY_PRINT)
. "\n";
exit;
}
-$req = new http\Client\Request ("POST",
- "http://" . $_SERVER["SERVER_NAME"] . "/backend/pay",
- array ("Content-Type" => "application/json"));
+
+// Backend is relative to the shop site.
+$url = (new http\URL("http://".$_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"]))
+ ->mod(array ("path" => "backend/pay"), http\Url::JOIN_PATH);
+
+$req = new http\Client\Request("POST",
+ $url,
+ array ("Content-Type" => "application/json"));
$req->getBody()->append (json_encode ($new_deposit_permission));
// Execute the HTTP request
@@ -104,7 +118,9 @@ else
{
$_SESSION['payment_ok'] = true;
http_response_code (301);
- header("Location: http://" . $_SERVER["SERVER_NAME"] . "/fullfillment");
+ $url = (new http\URL("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"))
+ ->mod(array ("path" => "fulfillment.php"), http\Url::JOIN_PATH);
+ header("Location: $url");
die();
}
diff --git a/src/frontend/style.css b/src/frontend/style.css
index 4346b0d0..c2cc51ee 100644
--- a/src/frontend/style.css
+++ b/src/frontend/style.css
@@ -63,3 +63,61 @@ h3 {
h4, h5, h6 {
font-size: 100%;
}
+
+.loader {
+ font-size: 10px;
+ margin: 50px auto;
+ text-indent: -9999em;
+ width: 11em;
+ height: 11em;
+ border-radius: 50%;
+ background: #ffffff;
+ background: -moz-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ background: -webkit-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ background: -o-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ background: -ms-linear-gradient(left, #000 10%, rgba(255, 255, 255, 0) 42%);
+ position: relative;
+ -webkit-animation: load3 1.4s infinite linear;
+ animation: load3 1.4s infinite linear;
+ -webkit-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+}
+
+.loader:after {
+ background: #fff;
+ width: 75%;
+ height: 75%;
+ border-radius: 50%;
+ content: '';
+ margin: auto;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
+
+@-webkit-keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index 8c9957c3..817ab93b 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -171,6 +171,7 @@ struct TALER_MERCHANT_PayCoin
* Pay a merchant. API for wallets that have the coin's private keys.
*
* @param merchant the merchant context
+ * @param merchant_uri URI of the merchant
* @param mint_uri URI of the mint that the coins belong to
* @param h_wire hash of the merchant’s account details
* @param h_contract hash of the contact of the merchant with the customer
@@ -181,12 +182,15 @@ struct TALER_MERCHANT_PayCoin
* @param num_coins number of coins used to pay
* @param coins array of coins we use to pay
* @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
* @param pay_cb the callback to call when a reply for this request is available
* @param pay_cb_cls closure for @a pay_cb
* @return a handle for this request
*/
struct TALER_MERCHANT_Pay *
TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
const char *mint_uri,
const struct GNUNET_HashCode *h_wire,
const struct GNUNET_HashCode *h_contract,
@@ -196,6 +200,8 @@ TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
struct GNUNET_TIME_Absolute refund_deadline,
unsigned int num_coins,
const struct TALER_MERCHANT_PayCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
TALER_MERCHANT_PayCallback pay_cb,
void *pay_cb_cls);
@@ -219,7 +225,7 @@ struct TALER_MERCHANT_PaidCoin
/**
* Coin's public key.
*/
- struct TALER_CoinSpendPrivateKeyP coin_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* Coin's signature key.
@@ -246,6 +252,7 @@ struct TALER_MERCHANT_PaidCoin
* in the type of @a coins compared to #TALER_MERCHANT_pay().
*
* @param merchant the merchant context
+ * @param merchant_uri URI of the merchant
* @param mint_uri URI of the mint that the coins belong to
* @param h_wire hash of the merchant’s account details
* @param h_contract hash of the contact of the merchant with the customer
@@ -253,15 +260,19 @@ struct TALER_MERCHANT_PaidCoin
* @param transaction_id transaction id for the transaction between merchant and customer
* @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
* @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed)
+ * @param execution_deadline date by which the merchant would like the mint to execute the transaction (can be zero if there is no specific date desired by the frontend)
* @param num_coins number of coins used to pay
* @param coins array of coins we use to pay
* @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
* @param pay_cb the callback to call when a reply for this request is available
* @param pay_cb_cls closure for @a pay_cb
* @return a handle for this request
*/
struct TALER_MERCHANT_Pay *
TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
const char *mint_uri,
const struct GNUNET_HashCode *h_wire,
const struct GNUNET_HashCode *h_contract,
@@ -269,8 +280,11 @@ TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant,
uint64_t transaction_id,
const struct TALER_MerchantPublicKeyP *merchant_pub,
struct GNUNET_TIME_Absolute refund_deadline,
+ struct GNUNET_TIME_Absolute execution_deadline,
unsigned int num_coins,
const struct TALER_MERCHANT_PaidCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
TALER_MERCHANT_PayCallback pay_cb,
void *pay_cb_cls);
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 8696db40..e43630de 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -42,6 +42,10 @@ test_merchant_api_SOURCES = \
test_merchant_api_LDADD = \
libtalermerchant.la \
$(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/util/libtalerutil.la \
+ -ltalermint \
+ -ltalerutil \
-lgnunetutil \
-ljansson
+
+EXTRA_DIST = \
+ test_merchant.conf
diff --git a/src/lib/merchant_api_context.c b/src/lib/merchant_api_context.c
index 5b8b9e4f..dd363b05 100644
--- a/src/lib/merchant_api_context.c
+++ b/src/lib/merchant_api_context.c
@@ -327,21 +327,29 @@ TALER_MERCHANT_perform (struct TALER_MERCHANT_Context *ctx)
*/
void
TALER_MERCHANT_get_select_info (struct TALER_MERCHANT_Context *ctx,
- fd_set *read_fd_set,
- fd_set *write_fd_set,
- fd_set *except_fd_set,
- int *max_fd,
- long *timeout)
+ fd_set *read_fd_set,
+ fd_set *write_fd_set,
+ fd_set *except_fd_set,
+ int *max_fd,
+ long *timeout)
{
+ long to;
+
GNUNET_assert (CURLM_OK ==
curl_multi_fdset (ctx->multi,
read_fd_set,
write_fd_set,
except_fd_set,
max_fd));
+ to = *timeout;
GNUNET_assert (CURLM_OK ==
curl_multi_timeout (ctx->multi,
- timeout));
+ &to));
+ /* Only if what we got back from curl is smaller than what we
+ already had (-1 == infinity!), then update timeout */
+ if ( (to < *timeout) &&
+ (-1 != to) )
+ *timeout = to;
if ( (-1 == (*timeout)) &&
(NULL != ctx->jobs_head) )
*timeout = 1000 * 60 * 5; /* curl is not always good about giving timeouts */
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
index e33da423..40818e99 100644
--- a/src/lib/merchant_api_pay.c
+++ b/src/lib/merchant_api_pay.c
@@ -66,6 +66,10 @@ struct TALER_MERCHANT_Pay
*/
struct MAC_DownloadBuffer db;
+ /**
+ * Reference to the merchant.
+ */
+ struct TALER_MERCHANT_Context *merchant;
};
@@ -81,6 +85,7 @@ static void
handle_pay_finished (void *cls,
CURL *eh)
{
+ /* FIXME: this function is not yet implemented!!! */
struct TALER_MERCHANT_Pay *ph = cls;
long response_code;
json_t *json;
@@ -125,6 +130,7 @@ handle_pay_finished (void *cls,
}
ph->cb (ph->cb_cls,
response_code,
+ "FIXME-redirect-URI",
json);
json_decref (json);
TALER_MERCHANT_pay_cancel (ph);
@@ -145,12 +151,15 @@ handle_pay_finished (void *cls,
* @param num_coins number of coins used to pay
* @param coins array of coins we use to pay
* @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
* @param pay_cb the callback to call when a reply for this request is available
* @param pay_cb_cls closure for @a pay_cb
* @return a handle for this request
*/
struct TALER_MERCHANT_Pay *
TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
const char *mint_uri,
const struct GNUNET_HashCode *h_wire,
const struct GNUNET_HashCode *h_contract,
@@ -159,18 +168,15 @@ TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
const struct TALER_MerchantPublicKeyP *merchant_pub,
struct GNUNET_TIME_Absolute refund_deadline,
unsigned int num_coins,
- const struct TLAER_MERCHANT_PayCoin *coins,
+ const struct TALER_MERCHANT_PayCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
TALER_MERCHANT_PayCallback pay_cb,
void *pay_cb_cls)
{
- struct TALER_MERCHANT_Pay *ph;
- json_t *pay_obj;
- json_t *j_coins;
- CURL *eh;
- struct GNUNET_HashCode h_wire;
- struct TALER_Amount amount_without_fee;
unsigned int i;
struct TALER_DepositRequestPS dr;
+ struct TALER_MERCHANT_PaidCoin pc[num_coins];
dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT);
dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS));
@@ -180,53 +186,220 @@ TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
dr.transaction_id = GNUNET_htonll (transaction_id);
dr.merchant = *merchant_pub;
- j_coins = json_array ();
for (i=0;i<num_coins;i++)
{
- json_t *j_coin;
- const struct TALER_MERCHANT_PayCoin *pc = &coins[i];
- struct TALER_CoinSpendSignatureP coin_sig;
+ const struct TALER_MERCHANT_PayCoin *coin = &coins[i];
+ struct TALER_MERCHANT_PaidCoin *p = &pc[i];
struct TALER_Amount fee;
/* prepare 'dr' for this coin to generate coin signature */
- GNUNET_CRYPTO_ecdhe_key_get_public (&pc->coin_priv.edche_priv,
- &dr.coin_pub.ecdhe_pub);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin->coin_priv.eddsa_priv,
+ &dr.coin_pub.eddsa_pub);
TALER_amount_hton (&dr.amount_with_fee,
&pc->amount_with_fee);
if (GNUNET_SYSERR ==
TALER_amount_subtract (&fee,
&pc->amount_with_fee,
- &pc->fee))
+ &pc->amount_without_fee))
{
/* Integer underflow, fee larger than total amount?
This should not happen (client violated API!) */
GNUNET_break (0);
- json_decref (j_coins);
return NULL;
}
TALER_amount_hton (&dr.deposit_fee,
&fee);
- GNUNET_CRYPTO_eddsa_sign (&pc->coin_priv.eddsa_priv,
+ GNUNET_CRYPTO_eddsa_sign (&coin->coin_priv.eddsa_priv,
&dr.purpose,
- &coin_sig.eddsa_sig);
+ &p->coin_sig.eddsa_signature);
+ p->denom_pub = coin->denom_pub;
+ p->denom_sig = coin->denom_sig;
+ p->coin_pub = dr.coin_pub;
+ p->amount_with_fee = pc->amount_with_fee;
+ p->amount_without_fee = pc->amount_without_fee;
+ }
+ return TALER_MERCHANT_pay_frontend (merchant,
+ merchant_uri,
+ mint_uri,
+ h_wire,
+ h_contract,
+ timestamp,
+ transaction_id,
+ merchant_pub,
+ refund_deadline,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ num_coins,
+ pc,
+ max_fee,
+ amount,
+ pay_cb,
+ pay_cb_cls);
+}
+
+
+/**
+ * Pay a merchant. API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures. Note the sublte difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ *
+ * @param merchant the merchant context
+ * @param mint_uri URI of the mint that the coins belong to
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract hash of the contact of the merchant with the customer
+ * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
+ * @param transaction_id transaction id for the transaction between merchant and customer
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed)
+ * @param execution_deadline date by which the merchant would like the mint to execute the transaction (can be zero if there is no specific date desired by the frontend)
+ * @param num_coins number of coins used to pay
+ * @param coins array of coins we use to pay
+ * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
+ * @param max_fee maximum fee covered by the merchant (according to the contract)
+ * @param amount total value of the contract to be paid to the merchant
+ * @param pay_cb the callback to call when a reply for this request is available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
+ */
+struct TALER_MERCHANT_Pay *
+TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant,
+ const char *merchant_uri,
+ const char *mint_uri,
+ const struct GNUNET_HashCode *h_wire,
+ const struct GNUNET_HashCode *h_contract,
+ struct GNUNET_TIME_Absolute timestamp,
+ uint64_t transaction_id,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Absolute refund_deadline,
+ struct GNUNET_TIME_Absolute execution_deadline,
+ unsigned int num_coins,
+ const struct TALER_MERCHANT_PaidCoin *coins,
+ const struct TALER_Amount *max_fee,
+ const struct TALER_Amount *amount,
+ TALER_MERCHANT_PayCallback pay_cb,
+ void *pay_cb_cls)
+{
+ struct TALER_MERCHANT_Pay *ph;
+ json_t *pay_obj;
+ json_t *j_coins;
+ CURL *eh;
+ struct TALER_Amount total_fee;
+ struct TALER_Amount total_amount;
+ unsigned int i;
+
+ if (0 == num_coins)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ j_coins = json_array ();
+ for (i=0;i<num_coins;i++)
+ {
+ json_t *j_coin;
+ const struct TALER_MERCHANT_PaidCoin *pc = &coins[i];
+ struct TALER_Amount fee;
+
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (&fee,
+ &pc->amount_with_fee,
+ &pc->amount_without_fee))
+ {
+ /* Integer underflow, fee larger than total amount?
+ This should not happen (client violated API!) */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ if (0 == i)
+ {
+ total_fee = fee;
+ total_amount = pc->amount_with_fee;
+ }
+ else
+ {
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&total_fee,
+ &total_fee,
+ &fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_add (&total_amount,
+ &total_amount,
+ &pc->amount_with_fee)) )
+ {
+ /* integer overflow */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ }
/* create JSON for this coin */
j_coin = json_pack ("{s:o, s:o," /* f/coin_pub */
" s:o, s:o," /* denom_pub / ub_sig */
" s:o}", /* coin_sig */
"f", TALER_json_from_amount (&pc->amount_with_fee),
- "coin_pub", TALER_json_from_data (&dr.coin_pub,
+ "coin_pub", TALER_json_from_data (&pc->coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP)),
"denom_pub", TALER_json_from_rsa_public_key (pc->denom_pub.rsa_public_key),
"ub_sig", TALER_json_from_rsa_signature (pc->denom_sig.rsa_signature),
- "coin_sig", TALER_json_from_data (&coin_sig,
- sizeof (coin_sig))
+ "coin_sig", TALER_json_from_data (&pc->coin_sig,
+ sizeof (struct TALER_CoinSpendSignatureP))
);
json_array_append (j_coins,
j_coin);
}
-
+ { /* Sanity check that total_amount and total_fee
+ match amount/max_fee requirements */
+ struct TALER_Amount fee_left;
+
+ if (GNUNET_OK ==
+ TALER_amount_subtract (&fee_left,
+ &total_fee,
+ max_fee))
+ {
+ /* Wallet must cover part of the fee! */
+ struct TALER_Amount new_amount;
+
+ if (GNUNET_OK !=
+ TALER_amount_add (&new_amount,
+ &fee_left,
+ amount))
+ {
+ /* integer overflow */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ if (1 ==
+ TALER_amount_cmp (&new_amount,
+ &total_amount))
+ {
+ /* new_amount > total_amount: all of the coins (total_amount)
+ do not add up to at least the new_amount owed to the
+ merchant, this request is bogus, abort */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ }
+ else
+ {
+ /* Full fee covered by merchant, but our total
+ must at least cover the total contract amount */
+ if (1 ==
+ TALER_amount_cmp (amount,
+ &total_amount))
+ {
+ /* amount > total_amount: all of the coins (total_amount) do
+ not add up to at least the amount owed to the merchant,
+ this request is bogus, abort */
+ GNUNET_break (0);
+ json_decref (j_coins);
+ return NULL;
+ }
+ }
+ } /* end of sanity check */
pay_obj = json_pack ("{s:o, s:o," /* H_wire/H_contract */
" s:I, s:o," /* transaction id, timestamp */
" s:o, s:s," /* refund_deadline, mint */
@@ -245,30 +418,19 @@ TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
"amount", TALER_json_from_amount (amount)
);
- // optionally: add edate! "edate", TALER_json_from_abs (wire_deadline),
+ if (0 != execution_deadline.abs_value_us)
+ {
+ /* Frontend did have an execution date in mind, add it */
+ json_object_set_new (pay_obj,
+ "edate",
+ TALER_json_from_abs (execution_deadline));
+ }
ph = GNUNET_new (struct TALER_MERCHANT_Pay);
-#if 0
ph->merchant = merchant;
- ph->cb = cb;
- ph->cb_cls = cb_cls;
- ph->url = MAH_path_to_url (merchant, "/pay");
- ph->depconf.purpose.size = htonl (sizeof (struct TALER_PayConfirmationPS));
- ph->depconf.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONFIRM_PAY);
- ph->depconf.h_contract = *h_contract;
- ph->depconf.h_wire = h_wire;
- ph->depconf.transaction_id = GNUNET_htonll (transaction_id);
- ph->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp);
- ph->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
- TALER_amount_subtract (&amount_without_fee,
- amount,
- &dki->fee_pay);
- TALER_amount_hton (&ph->depconf.amount_without_fee,
- &amount_without_fee);
- ph->depconf.coin_pub = *coin_pub;
- ph->depconf.merchant = *merchant_pub;
- ph->amount_with_fee = *amount;
- ph->coin_value = dki->value;
+ ph->cb = pay_cb;
+ ph->cb_cls = pay_cb_cls;
+ ph->url = GNUNET_strdup (merchant_uri);
eh = curl_easy_init ();
GNUNET_assert (NULL != (ph->json_enc =
@@ -295,56 +457,12 @@ TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant,
curl_easy_setopt (eh,
CURLOPT_WRITEDATA,
&ph->db));
- ctx = MAH_handle_to_context (merchant);
- ph->job = MAC_job_add (ctx,
+ ph->job = MAC_job_add (merchant,
eh,
GNUNET_YES,
&handle_pay_finished,
ph);
return ph;
-#endif
- return NULL;
-}
-
-
-
-/**
- * Pay a merchant. API for frontends talking to backends. Here,
- * the frontend does not have the coin's private keys, but just
- * the public keys and signatures. Note the sublte difference
- * in the type of @a coins compared to #TALER_MERCHANT_pay().
- *
- * @param merchant the merchant context
- * @param mint_uri URI of the mint that the coins belong to
- * @param h_wire hash of the merchant’s account details
- * @param h_contract hash of the contact of the merchant with the customer
- * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant
- * @param transaction_id transaction id for the transaction between merchant and customer
- * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
- * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed)
- * @param num_coins number of coins used to pay
- * @param coins array of coins we use to pay
- * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key.
- * @param pay_cb the callback to call when a reply for this request is available
- * @param pay_cb_cls closure for @a pay_cb
- * @return a handle for this request
- */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant,
- const char *mint_uri,
- const struct GNUNET_HashCode *h_wire,
- const struct GNUNET_HashCode *h_contract,
- struct GNUNET_TIME_Absolute timestamp,
- uint64_t transaction_id,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Absolute refund_deadline,
- unsigned int num_coins,
- const struct TALER_MERCHANT_PaidCoin *coins,
- TALER_MERCHANT_PayCallback pay_cb,
- void *pay_cb_cls)
-{
- GNUNET_break (0); // FIXME: not implemented!
- return NULL;
}
diff --git a/src/lib/test-mint-home/config/mint-common.conf b/src/lib/test-mint-home/config/mint-common.conf
new file mode 100644
index 00000000..b2b94826
--- /dev/null
+++ b/src/lib/test-mint-home/config/mint-common.conf
@@ -0,0 +1,30 @@
+[mint]
+# Currency supported by the mint (can only be one)
+CURRENCY = EUR
+
+# Wire format supported by the mint
+# We use 'test' for testing of the actual
+# coin operations, and 'sepa' to test SEPA-specific routines.
+WIREFORMAT = test sepa
+
+# HTTP port the mint listens to
+PORT = 8081
+
+# Master public key used to sign the mint's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+# Is this is a testcase, use transient DB actions?
+TESTRUN = YES
+
+[mintdb-postgres]
+
+DB_CONN_STR = "postgres:///talercheck"
+
+[mint-wire-sepa]
+SEPA_RESPONSE_FILE = "test-mint-home/sepa.json"
+
+[mint-wire-test]
+REDIRECT_URL = "http://www.taler.net/"
diff --git a/src/lib/test-mint-home/config/mint-keyup.conf b/src/lib/test-mint-home/config/mint-keyup.conf
new file mode 100644
index 00000000..8ad1f3bb
--- /dev/null
+++ b/src/lib/test-mint-home/config/mint-keyup.conf
@@ -0,0 +1,86 @@
+[mint_keys]
+
+# how long is one signkey valid?
+signkey_duration = 4 weeks
+
+# how long are the signatures with the signkey valid?
+legal_duration = 2 years
+
+# how long do we generate denomination and signing keys
+# ahead of time?
+lookahead_sign = 32 weeks 1 day
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 4 weeks 1 day
+
+
+# Coin definitions are detected because the section
+# name begins with "coin_". The rest of the
+# name is free, but of course following the convention
+# of "coin_$CURRENCY[_$SUBUNIT]_$VALUE" make sense.
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_10]
+value = EUR:10
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 1024
+
+[coin_eur_1000]
+value = EUR:1000
+duration_overlap = 5 minutes
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+rsa_keysize = 2048
diff --git a/src/lib/test-mint-home/master.priv b/src/lib/test-mint-home/master.priv
new file mode 100644
index 00000000..39492693
--- /dev/null
+++ b/src/lib/test-mint-home/master.priv
@@ -0,0 +1 @@
+p^-33XX!\0qmU_ \ No newline at end of file
diff --git a/src/lib/test-mint-home/sepa.json b/src/lib/test-mint-home/sepa.json
new file mode 100644
index 00000000..36d12f66
--- /dev/null
+++ b/src/lib/test-mint-home/sepa.json
@@ -0,0 +1,6 @@
+{
+ "receiver_name": "Max Mustermann",
+ "iban": "DE89370400440532013000",
+ "bic": "COBADEFF370",
+ "sig": "8M5YJXM68PRAXKH76HYEBCJW657B23JA0RFGNDMZK2379YZMT626H1BN89KC0M1KJBWGYEN5Z763Q0Y7MCTZQ6BPPT7D9KFCTW60C10"
+} \ No newline at end of file
diff --git a/src/lib/test_merchant.conf b/src/lib/test_merchant.conf
new file mode 100644
index 00000000..04d0e20c
--- /dev/null
+++ b/src/lib/test_merchant.conf
@@ -0,0 +1,55 @@
+# Sample configuration file for a merchant.
+[merchant]
+
+# Which port do we run the backend on? (HTTP server)
+PORT = 8082
+
+# FIXME: is this one used?
+HOSTNAME = localhost
+
+# Where is our private key?
+KEYFILE = test_merchant.priv
+
+# What currency does this backend accept?
+CURRENCY = KUDOS
+
+# FIXME: to be revised
+TRUSTED_MINTS = taler
+
+# How quickly do we want the mint to send us our money?
+# Used only if the frontend does not specify a value.
+# FIXME: EDATE is a bit short, 'execution_delay'?
+EDATE = 3 week
+
+# Which plugin (backend) do we use for the DB.
+DB = postgres
+
+[mint-taler]
+URI = http://localhost:8081/
+MASTER_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# Auditors must be in sections "auditor-", the rest of the section
+# name could be anything.
+[auditor-ezb]
+# Informal name of the auditor. Just for the user.
+NAME = European Central Bank
+
+# URI of the auditor (especially for in the future, when the
+# auditor offers an automated issue reporting system).
+# Not really used today.
+URI = http://taler.ezb.eu/
+
+# This is the important bit: the signing key of the auditor.
+PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
+
+# This specifies which database we use.
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+
+# "wire-" sections include wire details, here for SEPA.
+[wire-sepa]
+IBAN = DE67830654080004822650
+NAME = GNUNET E.V
+BIC = GENODEF1SRL
+SALT = 17919252168512238964
+ADDRESS = "Garching"
diff --git a/src/lib/test_merchant.priv b/src/lib/test_merchant.priv
new file mode 100644
index 00000000..9c18c358
--- /dev/null
+++ b/src/lib/test_merchant.priv
@@ -0,0 +1 @@
+`&-./ jxGݢO:6l,ζXT4 \ No newline at end of file
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index c264f9a3..82439432 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -19,14 +19,24 @@
* @author Christian Grothoff
*/
#include "platform.h"
-#include "taler_util.h"
-#include "taler_signatures.h"
-#include "taler_mint_service.h"
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_mint_service.h>
#include "taler_merchant_service.h"
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
/**
+ * URI under which the merchant is reachable during the testcase.
+ */
+#define MERCHANT_URI "http://localhost:8082/"
+
+/**
+ * URI under which the mint is reachable during the testcase.
+ */
+#define MINT_URI "http://localhost:8081"
+
+/**
* Main execution context for the main loop of the mint.
*/
static struct TALER_MINT_Context *ctx;
@@ -285,6 +295,11 @@ struct Command
const char *amount;
/**
+ * Maximum fee covered by merchant.
+ */
+ const char *max_fee;
+
+ /**
* Reference to a reserve_withdraw operation for a coin to
* be used for the /deposit operation.
*/
@@ -318,15 +333,9 @@ struct Command
struct GNUNET_TIME_Relative refund_deadline;
/**
- * Set (by the interpreter) to a fresh private key of the merchant,
- * if @e refund_deadline is non-zero.
- */
- struct TALER_MerchantPrivateKeyP merchant_priv;
-
- /**
* Deposit handle while operation is running.
*/
- struct TALER_MINT_DepositHandle *dh;
+ struct TALER_MERCHANT_Pay *ph;
} pay;
@@ -733,7 +742,7 @@ pay_cb (void *cls,
struct InterpreterState *is = cls;
struct Command *cmd = &is->commands[is->ip];
- cmd->details.deposit.dh = NULL;
+ cmd->details.pay.ph = NULL;
if (cmd->expected_response_code != http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -977,10 +986,102 @@ interpreter_run (void *cls,
return;
case OC_PAY:
{
- GNUNET_break (0); // FIXME: not implemented!
- trigger_context_task ();
+ struct TALER_MERCHANT_PayCoin pc;
+ struct TALER_Amount amount;
+ struct TALER_Amount max_fee;
+ json_t *wire;
+ json_t *contract;
+ struct GNUNET_HashCode h_wire;
+ struct GNUNET_HashCode h_contract;
+
+ /* get amount */
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.pay.amount,
+ &amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %u\n",
+ cmd->details.pay.amount,
+ is->ip);
+ fail (is);
+ return;
+ }
+
+ /* get max_fee */
+ if (GNUNET_OK !=
+ TALER_string_to_amount (cmd->details.pay.max_fee,
+ &max_fee))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse max_fee `%s' at %u\n",
+ cmd->details.pay.max_fee,
+ is->ip);
+ fail (is);
+ return;
+ }
+
+ /* parse wire details */
+ wire = json_loads (cmd->details.pay.wire_details,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == wire)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse wire details `%s' at %u\n",
+ cmd->details.pay.wire_details,
+ is->ip);
+ fail (is);
+ return;
+ }
+ TALER_hash_json (wire,
+ &h_wire);
+ json_decref (wire);
+
+ /* parse contract */
+ contract = json_loads (cmd->details.pay.contract,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == contract)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse contract details `%s' at %u\n",
+ cmd->details.pay.contract,
+ is->ip);
+ fail (is);
+ return;
+ }
+ TALER_hash_json (contract,
+ &h_contract);
+ json_decref (contract);
+
+ /* FIXME: fill in rest of arguments properly,
+ in particular merchant_pub and refund_deadline are
+ not correct right now... */
+ cmd->details.pay.ph
+ = TALER_MERCHANT_pay_wallet (merchant,
+ MERCHANT_URI,
+ MINT_URI,
+ &h_wire,
+ &h_contract,
+ GNUNET_TIME_absolute_get (),
+ cmd->details.pay.transaction_id,
+ NULL /* FIXME: merchant_pub */,
+ GNUNET_TIME_UNIT_ZERO_ABS /* refund dead */,
+ 1 /* num_coins */,
+ &pc /* coins */,
+ &max_fee,
+ &amount,
+ &pay_cb,
+ is);
+ }
+ if (NULL == cmd->details.pay.ph)
+ {
+ GNUNET_break (0);
+ fail (is);
return;
}
+ trigger_context_task ();
+ return;
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unknown instruction %d at %u (%s)\n",
@@ -1062,7 +1163,15 @@ do_shutdown (void *cls,
}
break;
case OC_PAY:
- GNUNET_break (0); // FIXME: not implemented
+ if (NULL != cmd->details.pay.ph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MERCHANT_pay_cancel (cmd->details.pay.ph);
+ cmd->details.pay.ph = NULL;
+ }
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -1089,6 +1198,11 @@ do_shutdown (void *cls,
TALER_MINT_disconnect (mint);
mint = NULL;
}
+ if (NULL != merchant)
+ {
+ TALER_MERCHANT_fini (merchant);
+ merchant = NULL;
+ }
if (NULL != ctx)
{
TALER_MINT_fini (ctx);
@@ -1152,6 +1266,7 @@ context_task (void *cls,
ctx_task = NULL;
TALER_MINT_perform (ctx);
+ TALER_MERCHANT_perform (merchant);
max_fd = -1;
timeout = -1;
FD_ZERO (&read_fd_set);
@@ -1163,6 +1278,12 @@ context_task (void *cls,
&except_fd_set,
&max_fd,
&timeout);
+ TALER_MERCHANT_get_select_info (merchant,
+ &read_fd_set,
+ &write_fd_set,
+ &except_fd_set,
+ &max_fd,
+ &timeout);
if (timeout >= 0)
delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
timeout);
@@ -1225,41 +1346,41 @@ run (void *cls,
{ .oc = OC_PAY,
.label = "deposit-simple",
.expected_response_code = MHD_HTTP_OK,
- .details.deposit.amount = "EUR:5",
- .details.deposit.coin_ref = "withdraw-coin-1",
- .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
- .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
- .details.deposit.transaction_id = 1 },
+ .details.pay.amount = "EUR:5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.pay.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
+ .details.pay.transaction_id = 1 },
/* Try to double-spend the 5 EUR coin with different wire details */
{ .oc = OC_PAY,
.label = "deposit-double-1",
.expected_response_code = MHD_HTTP_FORBIDDEN,
- .details.deposit.amount = "EUR:5",
- .details.deposit.coin_ref = "withdraw-coin-1",
- .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":43 }",
- .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
- .details.deposit.transaction_id = 1 },
+ .details.pay.amount = "EUR:5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":43 }",
+ .details.pay.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
+ .details.pay.transaction_id = 1 },
/* Try to double-spend the 5 EUR coin at the same merchant (but different
transaction ID) */
{ .oc = OC_PAY,
.label = "deposit-double-2",
.expected_response_code = MHD_HTTP_FORBIDDEN,
- .details.deposit.amount = "EUR:5",
- .details.deposit.coin_ref = "withdraw-coin-1",
- .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
- .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
- .details.deposit.transaction_id = 2 },
+ .details.pay.amount = "EUR:5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.pay.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }",
+ .details.pay.transaction_id = 2 },
/* Try to double-spend the 5 EUR coin at the same merchant (but different
contract) */
{ .oc = OC_PAY,
.label = "deposit-double-3",
.expected_response_code = MHD_HTTP_FORBIDDEN,
- .details.deposit.amount = "EUR:5",
- .details.deposit.coin_ref = "withdraw-coin-1",
- .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
- .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":2 } }",
- .details.deposit.transaction_id = 1 },
+ .details.pay.amount = "EUR:5",
+ .details.pay.coin_ref = "withdraw-coin-1",
+ .details.pay.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }",
+ .details.pay.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":2 } }",
+ .details.pay.transaction_id = 1 },
{ .oc = OC_END }
};
@@ -1269,10 +1390,12 @@ run (void *cls,
ctx = TALER_MINT_init ();
GNUNET_assert (NULL != ctx);
+ merchant = TALER_MERCHANT_init ();
+ GNUNET_assert (NULL != merchant);
ctx_task = GNUNET_SCHEDULER_add_now (&context_task,
ctx);
mint = TALER_MINT_connect (ctx,
- "http://localhost:8081",
+ MINT_URI,
&cert_cb, is,
TALER_MINT_OPTION_END);
GNUNET_assert (NULL != mint);
@@ -1322,7 +1445,7 @@ main (int argc,
NULL, NULL, NULL,
"taler-merchant-httpd",
"taler-merchant-httpd",
- "-c", "test-merchant-home",
+ "-c", "test_merchant.conf",
NULL);
/* give child time to start and bind against the socket */
fprintf (stderr, "Waiting for taler-mint-httpd to be ready");
@@ -1331,10 +1454,14 @@ main (int argc,
fprintf (stderr, ".");
sleep (1);
}
- while (0 != system ("wget -q -t 1 -T 1 http://127.0.0.1:8081/keys -o /dev/null -O /dev/null"));
+ while (0 != system ("wget -q -t 1 -T 1 " MINT_URI "/keys -o /dev/null -O /dev/null"));
fprintf (stderr, "\n");
result = GNUNET_SYSERR;
GNUNET_SCHEDULER_run (&run, NULL);
+ GNUNET_OS_process_kill (merchantd,
+ SIGTERM);
+ GNUNET_OS_process_wait (merchantd);
+ GNUNET_OS_process_destroy (merchantd);
GNUNET_OS_process_kill (mintd,
SIGTERM);
GNUNET_OS_process_wait (mintd);