diff options
author | Florian Dold <florian.dold@gmail.com> | 2015-12-23 00:00:17 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2015-12-23 00:00:17 +0100 |
commit | 96c3b85c6f12f2d0330180e5953ac8a1674c2731 (patch) | |
tree | c1d67f01454664a8395833ed220e19ca6e2cb46d | |
parent | cae3b8219845b15d890c24f46161f69f534200c3 (diff) | |
parent | 65cef9a1b4d958b59d93893ac3652c1a956e11e3 (diff) | |
download | merchant-96c3b85c6f12f2d0330180e5953ac8a1674c2731.tar.gz merchant-96c3b85c6f12f2d0330180e5953ac8a1674c2731.tar.bz2 merchant-96c3b85c6f12f2d0330180e5953ac8a1674c2731.zip |
Merge branch 'master' of ssh://taler.net/var/git/merchant
24 files changed, 870 insertions, 248 deletions
@@ -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); |