merchant-frontend-examples

ZZZ: Inactive/Deprecated
Log | Files | Refs

commit e51e36a052628cebd0cbafa9ad63bf0e9e3b8ad3
parent c0fd375b2a875c7fd50aaa13ef51aaab4ea2a08c
Author: Marcello Stanisci <marcello.stanisci@inria.fr>
Date:   Mon, 14 Nov 2016 23:29:07 +0100

PHP files

Diffstat:
Aphp/blog/articles/article_images.php | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/blog/blog_lib.php | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/blog/essay_cc-fulfillment.php | 36++++++++++++++++++++++++++++++++++++
Aphp/blog/essay_contract.php | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/blog/essay_fulfillment.php | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/blog/essay_pay.php | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/blog/images.php | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/config.php | 3+++
Aphp/copylib/config.php | 40++++++++++++++++++++++++++++++++++++++++
Aphp/copylib/merchants.php | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/copylib/util.php | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/doc/Makefile.am | 13+++++++++++++
Aphp/doc/manual.texi | 333+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/donate-handler.php | 12++++++++++++
Aphp/generate-contract.php | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/index.php | 12++++++++++++
Aphp/shop/checkout.php | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/shop/config.php | 20++++++++++++++++++++
Aphp/shop/fulfillment.php | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/shop/generate_taler_contract.php | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/shop/index.php | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aphp/shop/pay.php | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
22 files changed, 1863 insertions(+), 0 deletions(-)

diff --git a/php/blog/articles/article_images.php b/php/blog/articles/article_images.php @@ -0,0 +1,82 @@ +<?php + /** + * Parse $html_filename and add an entry of the type + * "$html_filename" => ("img1.png", "img2.png") for each + * encountered 'img' tag having the 'src' attribute formatted + * as "/essay/<article_filename>/data/img1.png", to the JSON which + * associates any article with its images. + * Note that <article_filename> has the final '.html' removed + */ + function add_article($html_filename){ + $doc = new DOMDocument(); + $doc->loadHTMLFile($html_filename); + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('php', 'http://php.net/xpath'); + $xpath->registerPhpFunctions('preg_match'); + $elements = $xpath->query('//img[php:functionString("preg_match", "@^/data/[^/]+/[^/]+@", @src) > 0]'); + $db_filename = "articles_images.json"; + $json_str; + if (file_exists($db_filename)) + $json_str = file_get_contents($db_filename); + else + $json_str = ""; + $json_db = json_decode($json_str); + $json_db->$html_filename = array(); + foreach($elements as $img){ + $value = $img->getAttributeNode("src")->value; + array_push($json_db->$html_filename, basename($value)); + } + file_put_contents($db_filename, json_encode($json_db)); + } + + /* suppress warnings due to parsing HTML5 */ + libxml_use_internal_errors(true); + + add_article("scrap1_10.html"); + add_article("scrap1_11.html"); + add_article("scrap1_12.html"); + add_article("scrap1_13.html"); + add_article("scrap1_14.html"); + add_article("scrap1_15.html"); + add_article("scrap1_16.html"); + add_article("scrap1_17.html"); + add_article("scrap1_18.html"); + add_article("scrap1_19.html"); + add_article("scrap1_1.html"); + add_article("scrap1_20.html"); + add_article("scrap1_21.html"); + add_article("scrap1_22.html"); + add_article("scrap1_23.html"); + add_article("scrap1_24.html"); + add_article("scrap1_25.html"); + add_article("scrap1_26.html"); + add_article("scrap1_27.html"); + add_article("scrap1_28.html"); + add_article("scrap1_29.html"); + add_article("scrap1_2.html"); + add_article("scrap1_30.html"); + add_article("scrap1_31.html"); + add_article("scrap1_32.html"); + add_article("scrap1_33.html"); + add_article("scrap1_34.html"); + add_article("scrap1_35.html"); + add_article("scrap1_36.html"); + add_article("scrap1_37.html"); + add_article("scrap1_38.html"); + add_article("scrap1_39.html"); + add_article("scrap1_3.html"); + add_article("scrap1_40.html"); + add_article("scrap1_41.html"); + add_article("scrap1_42.html"); + add_article("scrap1_43.html"); + add_article("scrap1_46.html"); + add_article("scrap1_47.html"); + add_article("scrap1_4.html"); + add_article("scrap1_5.html"); + add_article("scrap1_6.html"); + add_article("scrap1_7.html"); + add_article("scrap1_8.html"); + add_article("scrap1_9.html"); + add_article("scrap1_U.0.html"); + add_article("scrap1_U.1.html"); +?> diff --git a/php/blog/blog_lib.php b/php/blog/blog_lib.php @@ -0,0 +1,53 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + + /** + * Take a (article's) filename and return its + * teaser. It has the articles folder hardcoded + */ + function get_title($name){ + $content = file_get_contents("articles/$name.html"); + $doc = new DOMDocument(); + $doc->loadHTML($content); + $finder = new DOMXPath($doc); + $query_set = $finder->query("//h1[@class='chapter' or @class='unnumbered']"); + if (1 != $query_set->length) + return "No title for this item"; + // assuming all the articles are well-formed.. + return $query_set->item(0)->nodeValue; + } + + /** + * Take a (article's) filename and return its + * DOM. It has the articles folder hardcoded + */ + function get_article($name){ + $raw_content = file_get_contents("articles/$name.html"); + return $raw_content; + } + + /** + * Fetch the page $page and return its + * DOM. + */ + function get_page($page){ + $content = file_get_contents($page); + $doc = new DOMDocument(); + $doc->loadHTML($content); + return $doc; + } +?> diff --git a/php/blog/essay_cc-fulfillment.php b/php/blog/essay_cc-fulfillment.php @@ -0,0 +1,36 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> + + @author Marcello Stanisci +*/ + + include '../../copylib/util.php'; + include './blog_lib.php'; + + $article = get($_GET['article']); + if (null == $article){ + http_response_code(400); + echo "Bad request (no article specified)"; + return; + } + + session_start(); + $payments = &pull($_SESSION, "payments", array()); + $payments[$article] = array("ispayed" => true); + $fulfillment_url = url_rel("essay_fulfillment.php"); + header("Location: $fulfillment_url"); + die(); +?> diff --git a/php/blog/essay_contract.php b/php/blog/essay_contract.php @@ -0,0 +1,72 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + + include("../../copylib/merchants.php"); + include("../../copylib/util.php"); + include("../../copylib/config.php"); + include("./blog_lib.php"); + $article = get($_GET['article']); + if (null == $article){ + echo message_from_missing_param("article", "/"); + die(); + } + // send contract + $transaction_id = rand(0, 1001); + $now = new DateTime('now'); + $teaser = get_title($article); + $amount_value = 0; + $amount_fraction = 50000; + $teatax = array (); + $transaction_id = rand(0, 1001); + $fulfillment_url = url_rel("essay_fulfillment.php") + . '&timestamp=' . $now->getTimestamp() + . '&tid=' . $transaction_id; + + $contract_json = generate_contract(array( + "amount_value" => $amount_value, + "amount_fraction" => $amount_fraction, + "currency" => $MERCHANT_CURRENCY, + "refund_delta" => $REFUND_DELTA, + "transaction_id" => $transaction_id, + "description" => trim($teaser), + "merchant_name" => "Free Software Foundation (demo)", + "product_id" => $article, + "correlation_id" => $article, + "taxes" => $teatax, + "now" => $now, + "fulfillment_url" => $fulfillment_url) + ); + $resp = give_to_backend("backend/contract", + $contract_json); + $status_code = $resp->getResponseCode(); + http_response_code ($status_code); + if ($status_code != 200){ + echo json_encode(array( + 'error' => "internal error", + 'hint' => "backend indicated error", + 'detail' => $resp->body->toString() + ), JSON_PRETTY_PRINT); + } + else { + $got_json = json_decode($resp->body->toString(), true); + $hc = $got_json["H_contract"]; + session_start(); + $payments = &pull($_SESSION, "payments", array()); + $payments[$article] = array("ispayed" => false); + echo $resp->body->toString(); + } +?> diff --git a/php/blog/essay_fulfillment.php b/php/blog/essay_fulfillment.php @@ -0,0 +1,89 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + include '../../copylib/util.php'; + include '../../copylib/merchants.php'; + include '../../copylib/config.php'; + include './blog_lib.php'; + + $article = get($_GET['article']); + if (null == $article){ + http_response_code(400); + echo message_from_missing_param("article", "/"); + return; + } + session_start(); + //syslog($LOG_ERR, "merchant: official log system"); + $payments = &pull($_SESSION, 'payments', array()); + $my_payment = &pull($payments, $article, false); + + $pay_url = url_rel("essay_pay.php"); + $offering_url = url_rel("essay_fulfillment.php", true); + $offering_url .= "?article=$article"; + + // In PHP false == null + if (null == get($payments[$article]['ispayed']) || false == $my_payment){ + $tid = get($_GET['tid']); + $timestamp = get($_GET['timestamp']); + // 1st time + if (null == $tid || null == $timestamp){ + $js_code = "get_contract(\"$article\");"; + $cc_page = template("./essay_cc-form.html", array('article' => $article, 'jscode' => $js_code)); + echo $cc_page; + return; + } + // using deeplink (whether 1st time or not) + // restore contract + $now = new DateTime(); + $now->setTimestamp(intval($timestamp)); + + $contract_rec = generate_contract(array( + "amount_value" => 0, + "amount_fraction" => 50000, + "currency" => $MERCHANT_CURRENCY, + "refund_delta" => $REFUND_DELTA, + "merchant_name" => "Free Software Foundation (demo)", + "transaction_id" => intval($tid), + "description" => trim(get_title($article)), + "product_id" => $article, + "correlation_id" => $article, + "taxes" => array(), + "now" => $now, + "fulfillment_url" => get_full_uri()) + ); + $resp = give_to_backend("backend/contract", + $contract_rec); + if ($resp->getResponseCode() != 200){ + echo json_encode(array( + 'error' => "internal error", + 'hint' => "non hashable contract", + 'detail' => $resp->body->toString() + ), JSON_PRETTY_PRINT); + die(); + } + $hc = json_decode($resp->body->toString(), true)['H_contract']; + $my_payment['hc'] = $hc; + //syslog($LOG_INFO, "sending payment event"); + $js_code = "taler.executePayment(\"$hc\", \"$pay_url\", \"$offering_url\");"; + $cc_page = template("./essay_cc-form.html", array('article' => $article, 'jscode' => $js_code)); + echo $cc_page; + return; + } + // control here == article payed + //syslog($LOG_INFO, "showing article"); + $article = get_article($article); + echo $article; +?> diff --git a/php/blog/essay_pay.php b/php/blog/essay_pay.php @@ -0,0 +1,77 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + + include("../../copylib/merchants.php"); + include("../../copylib/util.php"); + include("./blog_lib.php"); + + /*FIXME: the following log gets annoyingly sent to _any_ open terminal */ + /*syslog($LOG_INFO, "paying");*/ + + $article = get($_GET["article"]); + if (empty($article)){ + http_response_code(400); + echo json_encode(array( + "error" => "missing parameter", + "parameter" => "article" + )); + return; + } + $deposit_permission = file_get_contents('php://input'); + session_start(); + if (!isset($_SESSION["payments"])) { + $json = json_encode( + array( + "error" => "no payments ongoing", + "status" => 500, + "detail" => "the shop has no state for any article" + ) + ); + echo $json; + die(); + } + $payments = &pull($_SESSION, "payments", array()); + $dec_dep_perm = json_decode($deposit_permission, true); + if ($dec_dep_perm['H_contract'] != $payments[$article]['hc']){ + $json = json_encode( + array( + "error" => "ill behaved wallet", + "status" => 400, + "detail" => "article payed differs from article to be shown" + ) + ); + echo $json; + die(); + } + + // with the article that's going to be payed + $resp = give_to_backend("backend/pay", + $deposit_permission); + $status_code = $resp->getResponseCode(); + http_response_code ($status_code); + if ($status_code != 200) + { + $json = json_encode( + array( + "error" => "backend error", + "status" => $status_code, + "detail" => $resp->body->toString())); + echo $json; + die(); + } + $payments[$article]['ispayed'] = true; +?> diff --git a/php/blog/images.php b/php/blog/images.php @@ -0,0 +1,58 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + + include '../../copylib/util.php'; + + $article = get($_GET['article']); + $image = get($_GET['image']); + session_start(); + $payments = &pull($_SESSION, 'payments', false); + if (!$payments) { + echo "No session active"; + return 400; + } + if (null == get($payments[$article]['ispayed'])) { + echo "Article not payed"; + return 400; + } + + $db_filename = "articles/articles_images.json"; + $json_str; + if (file_exists($db_filename)) + $json_str = file_get_contents($db_filename); + else { + echo "Internal server error: data registry not found"; + return 500; + } + $db = json_decode($json_str, true); + $article_images = get($db[$article . ".html"]); + if (null == $article_images) { + echo "This article has no images to sell"; + return 400; + } + if (false === array_search($image, $article_images)) { + echo "Requested image does not belong to article '$article'"; + return 400; + } + + $image_path = "data/" . $image; + $fp = fopen($image_path, 'rb'); + header("Content-Type: image/png"); // fix image extension + header("Content-Length: " . filesize($image_path)); + fpassthru($fp); + exit; +?> diff --git a/php/config.php b/php/config.php @@ -0,0 +1,3 @@ +<?php + $CONFIG = array("BACKEND_BASEURL" => "https://backend.demo.taler.net/") +?> diff --git a/php/copylib/config.php b/php/copylib/config.php @@ -0,0 +1,40 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +$REFUND_DELTA = 'P3M'; +// set to false when done with local tests +$explicit_currency = "PUDOS"; +$MERCHANT_CURRENCY = $explicit_currency; + +$host = $_SERVER["HTTP_HOST"]; +switch ($host) { +case "blog.demo.taler.net": +case "shop.demo.taler.net": + $MERCHANT_CURRENCY = "KUDOS"; + break; +case "blog.test.taler.net": +case "shop.test.taler.net": + $MERCHANT_CURRENCY = "PUDOS"; + break; +default: + if(false == $explicit_currency){ + http_response_code (500); + echo "<p>Bank configuration error: No currency for domain $host</p>\n"; + die(); + } +} +?> diff --git a/php/copylib/merchants.php b/php/copylib/merchants.php @@ -0,0 +1,105 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * Return a contract proposition to forward to the backend + * Note that `teatax` is an associative array representing a + * Taler-style amount (so it has the usual <amount,fration,currency> + * triple). Moreover, `teatax` should be a *list* of taxes + */ +function generate_contract($args){ + $contract = array ('amount' => + array ('value' => $args['amount_value'], + 'fraction' => $args['amount_fraction'], + 'currency' => $args['currency']), + 'max_fee' => + array ('value' => 3, + 'fraction' => 01010, + 'currency' => $args['currency']), + 'transaction_id' => $args['transaction_id'], + 'products' => array ( + array ('description' => $args['description'], + 'quantity' => 1, + 'price' => + array ('value' => $args['amount_value'], + 'fraction' => $args['amount_fraction'], + 'currency' => $args['currency']), + 'product_id' => $args['product_id'], + 'taxes' => $args['taxes'], + 'delivery_date' => "Some Date Format", + 'delivery_location' => 'LNAME1')), + 'timestamp' => "/Date(" . $args['now']->getTimestamp() . ")/", + 'expiry' => + "/Date(" . $args['now']->add(new DateInterval('P2W'))->getTimestamp() . ")/", + 'refund_deadline' => + "/Date(" . $args['now']->add(new DateInterval($args['refund_delta']))->getTimestamp() . ")/", + 'repurchase_correlation_id' => $args['correlation_id'], + 'fulfillment_url' => $args['fulfillment_url'], + 'merchant' => + array ('address' => 'LNAME2', + 'name' => $args['merchant_name'], + '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), JSON_PRETTY_PRINT); + return $json; +} + + +/** + * Feed `$json` to the backend and return the "(pecl) http response object" + * corresponding to the `$backend_relative_url` call + */ +function give_to_backend($backend_relative_url, $json){ + $url = url_join("http://".$_SERVER["HTTP_HOST"], $backend_relative_url); + + $req = new http\Client\Request("POST", + $url, + array ("Content-Type" => "application/json")); + + $req->getBody()->append($json); + + // Execute the HTTP request + $client = new http\Client; + $client->enqueue($req)->send(); + return $client->getResponse(); +} +?> diff --git a/php/copylib/util.php b/php/copylib/util.php @@ -0,0 +1,88 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +function get(&$var, $default=null) { + return isset($var) ? $var : $default; +} + +function &pull(&$arr, $idx, $default) { + if (!isset($arr[$idx])) { + $arr[$idx] = $default; + } + return $arr[$idx]; +} + +function message_from_missing_param($missing, $link, $link_name="home page"){ + return "<p>Bad request, no $missing given. Return to <a href=\"$link\">$link_name</a></p>"; +} + +function article_state_to_str($article_state){ + if(null == $article_state || !isset($article_state)) + return "undefined state"; + $str = "Is payed? "; + $str .= $article_state['ispayed'] ? "true," : "false,"; + if(!isset($article_state['hc'])) + $str .= " no hashcode for this article"; + else $str .= " " . $article_state['hc']; + return $str; +} + +function log_string($str){ + file_put_contents("/tmp/frontend.dbg", $str . "\n", FILE_APPEND); +} + +function get_full_uri(){ + + return $_SERVER['REQUEST_SCHEME'] . '://' + . $_SERVER['HTTP_HOST'] + . $_SERVER['REQUEST_URI']; +} + +function url_join($base, $path, $strip=false) { + $flags = $strip ? (http\Url::STRIP_PATH|http\URL::STRIP_QUERY) : 0; + return (new http\URL($base, null, $flags)) + ->mod(array ("path" => $path), http\Url::JOIN_PATH|http\URL::SANITIZE_PATH) + ->toString(); +} + +// Get a url with a path relative to the +// current script's path. +function url_rel($path, $strip=false) { + return url_join( + $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'], + $path, + $strip); +} + +function template($file, $array) { + if (file_exists($file)) { + $output = file_get_contents($file); + foreach ($array as $key => $val) { + $replace = '{'.$key.'}'; + $output = str_replace($replace, $val, $output); + } + return $output; + } +} + +function str_to_dom($str){ + $doc = new DOMDocument(); + $doc->loadHTML($str); + return $doc; + +} +?> diff --git a/php/doc/Makefile.am b/php/doc/Makefile.am @@ -0,0 +1,13 @@ +all: manual.pdf manual.html + +manual.pdf: arch.pdf manual.texi + texi2pdf manual.texi +manual.html: arch.jpg manual.texi + texi2html manual.texi +arch.pdf: arch.dot + dot -Tpdf arch.dot > arch.pdf +arch.jpg: arch.dot + dot -Tjpg arch.dot > arch.jpg + +info_TEXINFOS = manual.texi +manual_TEXINFOS = version.texi diff --git a/php/doc/manual.texi b/php/doc/manual.texi @@ -0,0 +1,333 @@ +\input texinfo @c -*-texinfo-*- +@c %**start of header +@setfilename manual.info +@include version.texi +@settitle The GNU Taler manual for Web shop operators @value{VERSION} + +@c Define a new index for options. +@defcodeindex op +@c Combine everything into one index (arbitrarily chosen to be the +@c concept index). +@syncodeindex op cp +@c %**end of header + +@copying +This manual is for the GNU Taler merchant backend (version @value{VERSION}, @value{UPDATED}), + +Copyright @copyright{} 2016 INRIA + +@quotation +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with no Front-Cover Texts, and with no Back-Cover +Texts. A copy of the license is included in the section entitled +``GNU Free Documentation License''. +@end quotation +@end copying +@c If your manual is published on paper by the FSF, it should include +@c The standard FSF Front-Cover and Back-Cover Texts, as given in +@c maintain.texi. +@c +@c Titlepage +@c +@titlepage +@title The GNU Taler manual for Web shops +@subtitle Version @value{VERSION} +@subtitle @value{UPDATED} +@author Marcello Stanisci (@email{marcello.stanisci@@inria.fr}) +@author Christian Grothoff (@email{christian.grothoff@@inria.fr}) +@page +@vskip 0pt plus 1filll +@insertcopying +@end titlepage + +@summarycontents +@contents + +@ifnottex +@node Top +@top The GNU Taler manual for Web shops +@insertcopying +@end ifnottex + +@menu +* Introduction:: Whom this manual is addressed to +* Hello-world:: How to set up a minimalistic shop +* Back-office-integration:: How to integrate with the back office +* Advanced topics:: Detailed solutions to specific issues +@end menu + + +@node Introduction +@chapter Introduction + +@section About GNU Taler + +GNU Taler is an open protocol for an electronic payment system with a +free software reference implementation. GNU Taler offers secure, fast +and easy payment processing using well understood cryptographic +techniques. GNU Taler allows customers to remain anonymous, while +ensuring that merchants can be held accountable by governments. +Hence, GNU Taler is compatible with anti-money-laundering (AML) and +know-your-customer (KYC) regulation, as well as data protection +regulation (such as GDPR). + + +@section About this manual + +This manual is for Web developers and addresses how to integrate GNU Taler +with Web shops. It describes how to create a Web shop that cooperates with +a GNU Taler merchant @emph{backend}. + +You can download all of the code examples given in this tutorial from +@url{https://git.taler.net/merchant-frontend-examples.git/tree/php/}. + +@c Add section giving an overview of what we will do in the tutorial! + + +@section Architecture overview + +The Taler software stack for a merchant consists of four main components: + +@itemize +@item A frontend which interacts with the customer's browser. The + frontend enables the customer to build a shopping cart and place + an order. Upon payment, it triggers the respective business logic + to satisfy the order. This component is not included with Taler, + but rather assumed to exist at the merchant. This manual + describes how to integrate Taler with Web shop frontends. +@item A back office application that enables the shop operators to + view customer orders, match them to financial transfers, and possibly + approve refunds if an order cannot be satisfied. This component is + again not included with Taler, but rather assumed to exist at the + merchant. This manual will describe how to integrate such a component + to handle payments managed by Taler. +@item A Taler-specific payment backend which makes it easy for the + frontend to process financial transactions with Taler. The + next two chapters will describe how to install and configure + this backend. +@item A DBMS which stores the transaction history for the Taler backend. + For now, the GNU Taler reference implemenation only supports Postgres, + but the code could be easily extended to support another DBMS. +@end itemize + +The following image illustrates the various interactions of these +key components: + +@center @image{arch, 3in, 4in} + + +Basically, the backend provides the cryptographic protocol support, +stores Taler-specific financial information in a DBMS and communicates +with the GNU Taler exchange over the Internet. The frontend accesses +the backend via a RESTful API. As a result, the frontend never has to +directly communicate with the exchange, and also does not deal with +sensitive data. In particular, the merchant's signing keys and bank +account information is encapsulated within the Taler backend. + + +@c FIXME: this should be a separate manual + + +@node Hello-world +@chapter Setting up a simple Web shop with GNU Taler + +This section describes how to setup a simple shop, which exposes a +button to get donations via Taler. The expected behaviour is that once +the ``donate'' button is clicked, the customer will receive a Taler +contract offering him the opportunity to make a fixed donation, +for example to donate 1 KUDOS to the charity operating the shop. + +@c NOTE: include explaining wallet installation to Web developer here! + +Note that if the customer does not have the Taler wallet installed, +they should instead be prompted to proceed with a classic dialog for +credit card payments. + + +@section Prompting for payment + +Our goal is to trigger a Taler payment once the customer has clicked +on a donation button. We will use a button that issues an HTTP POST +to the frontend @code{/donate} URL. For this, the HTML would be as +follows: + +@smallexample +<form action="/donate-handler"> + <input type="submit" value="Donate!"></input> +</form> +@end smallexample + +When the server-side handler for @code{/donate} receives the form submission, +it will return a HTML page that will take care of: + +@itemize +@item showing a credit card paywall to the user if no wallet is found, and +@item fetching a Taler contract and passing it to the wallet if one is found +@end itemize + +A minimalistic @code{/donate_handler} implementation is shown below (in PHP): + +@smallexample +// merchant/doc/examples/donate_handler.php +@include examples/donate_handler.php +@end smallexample + + +Given this response, the Taler wallet will fetch the contract from +@url{/generate-contract} and display it to the user. If the wallet is not +present, the HTML body will be shown and the Taler headers will be +ignored by the browser. Instead of specifying the contract via an +URL, it is also possible to inline short contracts directly. + +Note that we @emph{could} have bypassed the POST request to trigger +the payment, and performed the interaction with the wallet directly +from the button via JavaScript. + +@c We will consider this case in a later chapter. +@c FIXME: not yet ;-) + +@section Generating the contract + +The server-side handler for @code{/generate-contract} now has to +generate a contract proposal about donating 1 KUDOS to the 'Taler +charity program'. This proposed contract is then POSTed to the +backend at @code{/contract}. The main result of POSTing the proposal +to the backend is that it will be cryptographically signed. This is +necessary as by design the frontend does not perform any cryptographic +work. + +A simple @code{/generate-contract} handler may thus look like this: + +@smallexample +// merchant/doc/examples/generate_contract.php +@include examples/generate_contract.php +@end smallexample + +Note that in practice the frontend might want to generate a monotonically +increasing series of transaction IDs to avoid a chance of collisions. +Transaction IDs must be in the range of @math{[0:2^{51})}. + +The function @code{post_to_backend} is shown below; we will use it +again in other examples: + +@smallexample +// merchant/doc/examples/post_to_backend.php +@include examples/post_to_backend.php +@end smallexample + +After the browser has fetched the contract, the user will +be given the opportunity to affirm the payment. + + +@section Receiving payments via Taler + +The next step for the frontend is to accept the payment from the wallet, +assuming the user accepts the contract. For this, the frontend must +implement a payment handler at the URI specified for as the +@code{X-Taler-Pay-Url} in the example above. + +The role of the @code{/pay} handler is to receive the payment from +the wallet and forward it to the backend. If the backend reports +that the payment was successful, the handler needs to update the +session state with the browser to remember that the user paid. +The following code implements this in PHP: + +@smallexample +// merchant/doc/examples/pay_handler.php +@include examples/pay_handler.php +@end smallexample + +Do not be confused by the @code{isset} test for the session state. In +our simple example, it will be set to ``false'' by the fulfillment URL +which the browser actually always visits first.@footnote{This is for +technical reasons; the natural logical progression would of course be +to pay before accessing the fulfillment URL.} We describe how the +fulfillment URL works in the next section. + + +@section The fulfillment page + +The fulfillment page can be called by users that have already paid for +the item, as well as by users that have not yet paid at all. The +fulfillment page must use the HTTP session state to detect if the +payment has been performed already, and if not request payment from +the wallet. + +For our example, we include in the URI of the fulfillment page the data +which is needed to allow the page to determine which contract the user is +trying to access. +Thus, the fulfillment URL for our example looks like the following:@* + +@smallexample +https://shop.com/fulfillment? \ +transaction_id=<TRANSACTION_ID>&timestamp=<CONTRACTTIMESTAMP> +@end smallexample + + +@*The @code{/fulfillment} handler will then perform the following actions: + +@smallexample +// merchant/doc/examples/fulfillment_handler.php +@include examples/fulfillment_handler.php +@end smallexample + + +@node Back-office-integration +@chapter Integration of GNU Taler with the back office + + +@node Advanced topics +@chapter Advanced topics + +@section Payments using JavaScript + +The function @code{executePayment} exported by @code{taler-wallet-lib.js} will basically +hand its three parameters to the wallet which implements the following semantics:@* +check in the internal DB if @code{$response['H_contract']} has an entry, and: +@itemize +@item if that is the case, then the user accepted the contract previously and the wallet +sends a deposit permission @footnote{Roughly speaking, a deposit permission is a JSON +containing the coins to pay for a contract. Its full specification is available at: +@url{https://api.taler.net/api-merchant.html#depositpermission}} to @code{/frontend-pay}. +If this operation succeeds, then visit again the fulfillment URL, and finally enjoy +the product. +@item if not, redirect the browser to @code{/donate} (which will then reinitiate the +whole contract negotiation). This happens when the user visits a shared fulfillment URL. +The idea is to let that user buy the same products as the user who shared the fulfillment +URL. Nonetheless, the shop is not forced to follow this semantics when provides the third +parameter to @code{executePayment}. +@end itemize + + + +@section Design considerations for the fulfillment page + +The fulfillment page mechanism is designed to provide the following two properties: + +@enumerate +@item Taler payments @emph{can} be implemented in DB-less frontends. + +@item Taler payments are replayable, meaning that each purchase is associated with +a URL (the fulfillment URL) that shows the product each time it gets visited (and +of course, only the first time takes the user's money). +@end enumerate + +In order to implement property (1), the frontend needs a way to recall +what a contract is about (e.g. which product, which price, the +timestamp, etc.) before proceeding with the actual payment and +eventually deliver the final product. That is achieved by +@emph{reconstructing} the contract using the fulfillment page's URL +parameters@footnote{the fulfillment URL equipped with all the +parameters is included in the contract}. + +In order to implement property (2), the frontend will firstly check +the state to see if the product claimed among the fulfillment URL +parameter has been paid; if so, the product is given to the +customer. Otherwise, the frontend sets the payment as "pending" in the +state and @emph{executes} it in the wallet. The payment execution is +performed by returning JavaScript code from @code{taler-wallet-lib.js} +that triggers the wallet to send the payment to the pay page. Once +the pay page receives the payment, it sets the state for the payment +as "payed". diff --git a/php/donate-handler.php b/php/donate-handler.php @@ -0,0 +1,12 @@ +<?php + http_response_code (402); // 402: Payment required + header ('X-Taler-Contract-Url: /php/generate-contract.php'); + echo "<html> + <head> + <title>Select payment method</title> + </head> + <body> + Here you should put the HTML for the non-Taler (credit card) payment. + </body> + </html>"; +?> diff --git a/php/generate-contract.php b/php/generate-contract.php @@ -0,0 +1,121 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + + include 'config.php'; + + function make_contract($transaction_id, $now){ + $contract = array ('amount' => + array ('value' => 1, + 'fraction' => 0, + 'currency' => "KUDOS"), + 'max_fee' => + array ('value' => 0, + 'fraction' => 50000, + 'currency' => "KUDOS"), + 'transaction_id' => $transaction_id, + 'products' => array ( + array ('description' => "Donation to charity program", + 'quantity' => 1, + 'price' => + array ('value' => 1, + 'fraction' => 0, + 'currency' => "KUDOS"), + 'product_id' => 0, + 'taxes' => array(), + 'delivery_date' => "/Date(" . $now->getTimestamp() . ")/", + 'delivery_location' => 'LNAME1')), + 'timestamp' => "/Date(" . $now->getTimestamp() . ")/", + 'expiry' => + "/Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")/", + 'refund_deadline' => + "/Date(" . $now->add(new DateInterval('P3M'))->getTimestamp() . ")/", + 'repurchase_correlation_id' => $args['correlation_id'], + 'fulfillment_url' => $_SERVER['REQUEST_SCHEME'] + .'://' + .$_SERVER['HTTP_HOST'] + ."/fulfillment.php?" + . "transaction_id=$transaction_id&timestamp" + . $now->getTimestamp(), + 'merchant' => + array ('address' => 'LNAME2', + 'name' => "Charity donation shop", + 'jurisdiction' => 'LNAME3'), + 'locations' => + array ('LNAME1' => + array ('country' => 'Test Country 1', + 'city' => 'Test City 1', + 'state' => 'Test State 1', + 'region' => 'Test Region 1', + 'province' => 'Test Province 1', + 'ZIP code' => 4908 1, + 'street' => 'test street 1', + 'street number' => 201), + 'LNAME2' => + array ('country' => 'Test Country 2', + 'city' => 'Test City 2', + 'state' => 'Test State 2', + 'region' => 'Test Region 2', + 'province' => 'Test Province 2', + 'ZIP code' => 4908 2, + 'street' => 'test street 2', + 'street number' => 202), + 'LNAME3' => + array ('country' => 'Test Country 3', + 'city' => 'Test City 3', + 'state' => 'Test State 3', + 'region' => 'Test Region 3', + 'province' => 'Test Province 3', + 'ZIP code' => 49083))); + $json = json_encode (array ('contract' => $contract), JSON_PRETTY_PRINT); + return $json; + } + + + /** + * Feed `$json` to the backend and return the "(pecl) http response object" + * corresponding to the `$backend_relative_url` call + */ + function post_to_backend($backend_relative_url, $json){ + $url = url_join("http://".$CONFIG["BACKEND_BASEURL"], $backend_relative_url); + $req = new http\Client\Request("POST", + $url, + array ("Content-Type" => "application/json")); + $req->getBody()->append($json); + // Execute the HTTP request + $client = new http\Client; + $client->enqueue($req)->send(); + return $client->getResponse(); + } + + + /* this variable is the JSON of a contract proposal, + see https://api.taler.net/api-merchant.html#post--contract + the second parameter is the transaction id */ + $transaction_id = rand(1,90000); // simplified, do not do this! + $proposal = make_contract($transaction_id, new DateTime('now')); + # Here the frontend POSTs the proposal to the backend + $response = post_to_backend("/contract", $proposal); + if (200 != $response->getResponseCode()) { + echo json_encode(array( + ’error’ => "internal error", + ’hint’ => "failed to generate contract", + ’detail’ => $resp->body->toString() + ), JSON_PRETTY_PRINT); + return; + } + echo $response->body; +?> diff --git a/php/index.php b/php/index.php @@ -0,0 +1,12 @@ +<?php + echo "<html> + <head> + <title>Taler tutorial</title> + </head> + <body> + <form action='/php/donate-handler.php'> + <input type='submit' value='Donate!'></input> + </form> + </body> + </html>"; +?> diff --git a/php/shop/checkout.php b/php/shop/checkout.php @@ -0,0 +1,183 @@ +<!DOCTYPE html> +<!-- + This file is aprt of GNU TALER + Copyright (C) 2014,2015, 2016 INRIA + + TALER 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 3 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 3 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. + +--> +<html> +<head> + <title>Toy Store - Payment method - Taler Demo</title> + <link rel="stylesheet" type="text/css" href="web-common/style.css"> + <script type="application/javascript" src="web-common/taler-presence.js"></script> +</head> +<body> + +<?php + // get the donation information from form + $donation_receiver = $_POST['donation_receiver']; + $donation_amount = $_POST['donation_amount']; + $donation_currency = $_POST['donation_currency']; + + // get frational part + 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(); + $_SESSION['receiver'] = $donation_receiver; + $_SESSION['amount_value'] = (int) $donation_amount; + $_SESSION['amount_fraction'] = (int) ($donation_fraction * 1000000); + $_SESSION['currency'] = $donation_currency; +?> + + <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> + <!--#include virtual="web-common/dropdown-navbar.html" --> + <h1 class="nav">Toy Store - Select payment method</h1> + </header> + + <aside class="sidebar" id="left"> + </aside> + + <section id="main"> + <article> + <h1>Select your payment method</h1> + + <p> + This is an example for a "checkout" page of a Web shop. + On the previous page, you have created the shopping cart + and decided which product to buy (i.e. which project to + donate KUDOS to). Now in this page, you are asked to + select a payment option. As Taler is not yet universally + used, we expect merchants will offer various payment options. + </p> + <p> + The page also demonstrates how to only enable (or show) the Taler + option if Taler is actually supported by the browser. For example, + if you disable the Taler extension now, the Taler payment option + will be disabled in the page. Naturally, you could also trivially + hide the Taler option entirely by changing the visibility instead. + </p> + <p> + Note that you MUST select Taler here for the demo to continue, + as the other payment options are just placeholders and not + really working in the demonstration. Also, it is of course + possible to ask the user to make this choice already on the + previous page (with the shopping cart), we just separated the + two steps to keep each step as simple as possible. + </p> + + <form name="tform" action="" method="POST"> + <div id="opt-form" align="left"><br> + <input type="radio" name="payment_system" value="lisa" + id="lisa-radio-button-id">Lisa</input> + <br/> + <input type="radio" name="payment_system" value="ycard">You Card</input> + <br/> + <input type="radio" name="payment_system" value="cardme">Card Me</input> + <br/> + <input type="radio" name="payment_system" value="taler" + checked + id="taler-radio-button-id" class="taler-installed-enable">Taler</input> + <br/> + <input type="button" onclick="pay(this.form)" value="Ok"></input> + </div> + </form> + + </article> + </section> + +<script type="text/javascript"> + +/* This function is called from "taler_pay" after + we downloaded the JSON contract from the merchant. + We now need to pass it to the extension. */ +function handle_contract(contract_wrapper) { + var cEvent = new CustomEvent('taler-confirm-contract', { + detail: { + contract_wrapper: contract_wrapper + } + }); + document.dispatchEvent(cEvent); +}; + + +/* Trigger Taler contract generation on the server, and pass the + contract to the extension once we got it. */ +function taler_pay(form) { + var contract_request = new XMLHttpRequest(); + + /* Note that the URL we give here is specific to the Demo-shop + and not dictated by the protocol: each web shop can + 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.onload = function (e) { + if (contract_request.readyState == 4) { + if (contract_request.status == 200) { + /* display contract_requestificate (i.e. it sends the JSON string + to the extension) alert (contract_request.responseText); */ + console.log("response text:", contract_request.responseText); + var contract_wrapper = JSON.parse(contract_request.responseText); + if (!contract_wrapper) { + console.error("response text is invalid JSON"); + return; + } + handle_contract(contract_wrapper); + } else { + /* There was an error obtaining the contract from the merchant, + obviously this should not happen. To keep it simple, we just + alert the user to the error. */ + alert("Failure to download contract from merchant " + + "(" + contract_request.status + "):\n" + + contract_request.responseText); + } + } + }; + contract_request.onerror = function (e) { + /* There was an error obtaining the contract from the merchant, + obviously this should not happen. To keep it simple, we just + alert the user to the error. */ + alert("Failure requesting the contract:\n" + contract_request.statusText); + }; + contract_request.send(); +} + + +/* This function is called when the user presses the + 'Ok' button. We are now supposed to trigger the + "correct" payment system logic. For this demo, we + only handle "taler". */ +function pay(form) { + var choice = form.elements["payment_system"].value; + if (choice == "taler") { + taler_pay(form); + } + else { + alert("You selected '" + choice + "', but we do not support this payment system in the demo."); + } +}; + +</script> +</body> +</html> diff --git a/php/shop/config.php b/php/shop/config.php @@ -0,0 +1,20 @@ +<?php +/* This file is in the public domain */ + +$host = $_SERVER["HTTP_HOST"]; + +switch ($host) { +case "shop.demo.taler.net": + $SHOP_CURRENCY = "KUDOS"; + break; +case "shop.test.taler.net": + $SHOP_CURRENCY = "PUDOS"; + break; +default: + http_response_code ($status_code); + echo "<p>Configuration error: No currency for domain $host</p>\n"; + die(); + break; +} + +?> diff --git a/php/shop/fulfillment.php b/php/shop/fulfillment.php @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<!-- + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +--> +<html lang="en"> +<head> + <title>Taler's "Demo" Shop</title> + <link rel="stylesheet" type="text/css" href="web-common/style.css"> + <script type="application/javascript" src="web-common/taler-wallet-lib.js"></script> + <script type="application/javascript"> + function makeVisible() { + function cb() { + document.body.style.display = ""; + } + document.addEventListener("DOMContentLoaded", cb, false); + } + </script> +</head> +<body style="display:none;"> + <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> + <!--#include virtual="web-common/dropdown-navbar.html"--> + <h1 class="nav">Toy Store - Product Page</h1> + </header> + + <aside class="sidebar" id="left"> + </aside> + + <section id="main"> + <article> +<?php + +include '../../copylib/util.php'; +include "../../copylib/merchants.php"; + +$receiver = get($_GET["receiver"]); +$now = new DateTime(); +$now->setTimestamp(intval(get($_GET["timestamp"]))); + +if (empty($receiver)) { + http_response_code(400); + echo "<p>Bad request (UUID missing)</p>"; + return; +} + +session_start(); + +$payments = &pull($_SESSION, 'payments', array()); +$my_payment = &pull($payments, $receiver, array()); + +// This will keep the query parameters. +$pay_url = url_rel("pay.php"); +$offering_url = url_rel("index.php", true); + +if (array() === $my_payment || true !== get($my_payment["is_payed"], false)) { + // restore contract + $contract = generate_contract(array( + "amount_value" => intval($_GET['aval']), + "amount_fraction" => intval($_GET['afrac']), + "currency" => $_GET['acurr'], + "refund_delta" => 'P3M', + "transaction_id" => intval($_GET['tid']), + "description" => "Donation to " . $receiver, + "product_id" => "unused", + "correlation_id" => "", + "merchant_name" => "Kudos Inc.", + "taxes" => array(), + "now" => $now, + "fulfillment_url" => get_full_uri()) + ); + + $resp = give_to_backend("backend/contract", $contract); + if ($resp->getResponseCode() != 200){ + echo json_encode(array( + 'error' => "internal error", + 'hint' => "failed to regenerate contract", + 'detail' => $resp->body->toString() + ), JSON_PRETTY_PRINT); + return; + } + + $hc = json_decode($resp->body->toString(), true)['H_contract']; + $my_payment['is_payed'] = false; + $my_payment['hc'] = $hc; + echo "<p>you have not payed for this contract: " . $hc . "</p>"; + echo "<p>Asking the wallet to re-execute it ... </p>"; + echo "<script>taler.executePayment('$hc', '$pay_url', '$offering_url');</script>"; + return; +} + +$news = false; +switch ($receiver) { + case "Taler": + $news = "https://taler.net/news"; + break; + case "GNUnet": + $news = "https://gnunet.org/"; + break; + case "Tor": + $news = "https://www.torproject.org/press/press.html.en"; + break; +} + +$msg = "<p>Thanks for donating to " . $receiver . ".</p>"; +if ($news) { + $msg .= "<p>Check our latest <a href=\"" . $news . "\">news!</a></p>"; +} + +echo $msg; + +echo "<script>makeVisible();</script>"; + +?> + </article> + </section> +</body> +</html> diff --git a/php/shop/generate_taler_contract.php b/php/shop/generate_taler_contract.php @@ -0,0 +1,92 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014-2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> + */ + +include '../../copylib/util.php'; +include "../../copylib/config.php"; +include "../../copylib/merchants.php"; + +session_start(); + +if (!isset($_SESSION['receiver'])) +{ + http_response_code(400); + die(); +} + +$receiver = $_SESSION['receiver']; +$amount_value = intval($_SESSION['amount_value']); +$amount_fraction = intval($_SESSION['amount_fraction']); +$currency = $_SESSION['currency']; + +// generate a front-end transaction id. +// In production context, we might want to +// record this value somewhere together +// with the rest of the contract data. +$transaction_id = rand(0, 2<<40); + +// Human-readable description of this deal +$desc = "Donation to " . $receiver; + +// Take a timestamp +$now = new DateTime('now'); + +// Include all information so we can +// restore the contract without storing it +$fulfillment_url = url_rel("fulfillment.php") + . '?timestamp=' . $now->getTimestamp() + . '&receiver=' . urlencode($receiver) + . '&aval=' . urlencode($amount_value) + . '&afrac=' . urlencode($amount_fraction) + . '&acurr=' . urlencode($currency) + . '&tid=' . $transaction_id; + +$contract = generate_contract(array( + "amount_value" => $amount_value, + "amount_fraction" => $amount_fraction, + "currency" => $currency, + "refund_delta" => 'P3M', + "transaction_id" => $transaction_id, + "description" => $desc, + "product_id" => "unused", + "correlation_id" => "", + "merchant_name" => "Kudos Inc.", + "taxes" => array(), + "now" => $now, + "fulfillment_url" => $fulfillment_url) +); + +$resp = give_to_backend("backend/contract", $contract); + +// Our response code is the same we got from the backend: +http_response_code($resp->getResponseCode()); + +// Now generate our body +if ($resp->getResponseCode() != 200) +{ + echo json_encode(array( + 'error' => "internal error", + 'hint' => "backend indicated error", + 'detail' => $resp->body->toString() + ), JSON_PRETTY_PRINT); +} +else +{ + # no state here + $got_json = json_decode($resp->body->toString(), true); + echo json_encode ($got_json, JSON_PRETTY_PRINT); +} +?> diff --git a/php/shop/index.php b/php/shop/index.php @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<!-- + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +--> +<?php +require_once "../../copylib/config.php"; +session_destroy(); +?> +<html lang="en"> +<head> + <title>Taler Donation Demo</title> + <link rel="stylesheet" type="text/css" href="web-common/style.css"> + <script src="web-common/taler-presence.js" type="text/javascript"></script> +<script type="text/javascript"> +<?php +echo "\tvar shop_currency = '$MERCHANT_CURRENCY';\n"; +?> + + function addOption(value, label) { + var s = document.getElementById("taler-donation"); + var e = document.createElement("option"); + e.textContent = label ? label : ("".concat(value, " ", shop_currency)); + e.value = value; + s.appendChild(e); + } + + function init() { + var e = document.getElementById("currency-input"); + e.value = shop_currency; + addOption("0.1"); + addOption("1.0"); + addOption("6.0", "".concat(5, " ", shop_currency)); + addOption("10"); + } + + document.addEventListener("DOMContentLoaded", init); + +</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> + <!--#include virtual="web-common/dropdown-navbar.html" --> + <h1 class="nav">Toy &quot;Store&quot; - Taler Demo</h1> + </header> + + <aside class="sidebar" id="left"> + </aside> + + <section id="main"> + <article> + <h1>Welcome to the Taler Donation "Shop" Demo</h1> + + <p>This "toy" website provides you with the ability to + experience using the + <a href="https://www.taler.net/">GNU Taler</a> + payment system without using + valuable currency. Instead, for the demonstrator we + will be using a "toy" currency, KUDOS. However, please remember that + Taler is designed to work with ordinary currencies, such + as Dollars or Euros, not just toy currencies. + <br> + This page, <tt>shop.demo.taler.net</tt> models the behavior of a + typical Web shop supporting Taler. The other pages of the demo, + <tt>exchange.demo.taler.net</tt> and + <tt>bank.demo.taler.net</tt>, correspond to a Taler exchange + and bank with tight Taler integration respectively. You + may also enjoy visiting the <tt>blog.demo.taler.net</tt>. + </p> + </article> + + <div class="taler-installed-hide"> + <h2>Installing the Taler wallet</h2> + First, you need to install the Taler wallet browser extension. + Install the wallet + <span id="install-done" style="visibility: hidden">(done)</span> + <ul> + <li>from the app store for <a href="https://chrome.google.com/webstore/detail/gnu-taler-wallet/millncjiddlpgdmkklmhfadpacifaonc">Google + Chrome and Chromium</a> + </li> + <li>By visiting our <a href="/landing">landing page</a>. + </li> + </ul> + Wallets for other browsers will be provided in the near future. + </div> + + <div class="taler-installed-show"> + <p>Please choose a project and the amount of KUDOS you + wish to donate:</p> + + <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> + <br> + <input type="radio" name="donation_receiver" value="Tor">Tor</input> + <br> + <input type="radio" name="donation_receiver" value="GNUnet">GNUnet</input> + <br> + <select id="taler-donation" name="donation_amount"> + <!-- options will be added dynamically --> + </select> + <input id="currency-input" type="hidden" name="donation_currency"/> + <input type="submit" name="keyName" value="Donate!"/> + <br> + <br> + </div> + </form> + <p> + (*) To make it a bit more fun, the 5 KUDOS option + is deliberately implemented with a fault: the merchant will try to + make you donate 6 KUDOS instead of the 5 KUDOS you got to see. But do + not worry, you will be given the opportunity to review the + final offer from the merchant in a window secured + by the Taler extension. That way, you can spot the + error before committing to an incorrect contract. + </p> + </div> + </section> +</body> +</html> diff --git a/php/shop/pay.php b/php/shop/pay.php @@ -0,0 +1,101 @@ +<?php +/* + This file is part of GNU TALER. + Copyright (C) 2014, 2015, 2016 INRIA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> + +*/ + +include '../../copylib/util.php'; +log_string("getting money"); +// so we won't generate a response for the wrong receiver. +$receiver = get($_GET["receiver"]); +if (empty($receiver)) +{ + http_response_code(400); + echo json_encode(array( + "error" => "missing parameter", + "parameter" => "receiver" + )); + return; +} + +session_start(); +$payments = &pull($_SESSION, "payments", array()); + +if (!isset($payments[$receiver])) +{ + http_response_code(400); + echo json_encode(array( + "error" => "no payment session active" + )); + return; +} + +$post_body = file_get_contents('php://input'); +$deposit_permission = json_decode ($post_body, true); + +// Check if the receiver is actually *mentioned* in the contract +if ($payments[$receiver]['hc'] != $deposit_permission['H_contract']) { + + $json = json_encode( + array( + "error" => "ill behaved wallet", + "status" => 400, + "detail" => "deposit permission mismatches with reconstructed contract" + ) + ); + echo $json; + die(); +} + + +/* Craft the HTTP request, note that the backend + could be on an entirely different machine if + desired. */ + +// Backend is relative to the shop site. +$url = url_rel("backend/pay"); + +$req = new http\Client\Request("POST", + $url, + array("Content-Type" => "application/json")); +$req->getBody()->append (json_encode ($deposit_permission)); + +// Execute the HTTP request to the backend +$client = new http\Client; +$client->enqueue($req)->send(); + +// Fetch the response +$resp = $client->getResponse(); +$status_code = $resp->getResponseCode(); + +// Our response code is the same we got from the backend: +http_response_code($status_code); + +// Now generate our body +if ($status_code != 200) +{ + $json = json_encode( + array( + "error" => "backend error", + "status" => $status_code, + "detail" => $resp->body->toString())); + echo $json; + die(); +} + +$payments = &pull($_SESSION, "payments", array()); +$payments[$receiver]['is_payed'] = true; + +?>