gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-donations] branch master updated (92b5e50 -> 3e55361


From: gnunet
Subject: [GNUnet-SVN] [taler-donations] branch master updated (92b5e50 -> 3e55361)
Date: Fri, 24 Nov 2017 20:24:58 +0100

This is an automated email from the git hooks/post-receive script.

marcello pushed a change to branch master
in repository donations.

    from 92b5e50  base template
     new 35178b0  setuptools
     new 23d289f  __init__
     new f7f9578  actual donations logic
     new c660286  copy-paste leftover
     new 55e80d2  copy-paste leftover
     new 1bc7df8  config parser
     new 424891b  __init__ in the right place
     new 672ac93  helper module
     new 3e55361  missing templates

The 9 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 Makefile.in                                        |  10 +-
 setup.py                                           |  27 ++
 talerdonations/__init__.py                         |   0
 talerdonations/donations/__init__.py               |   3 +
 talerdonations/donations/donations.py              | 212 ++++++++++++++
 talerdonations/donations/templates/backoffice.html |  46 +++
 talerdonations/donations/templates/checkout.html   |  56 ++++
 .../donations/templates/execute-payment.html       |  12 +
 talerdonations/donations/templates/fallback.html   |  23 ++
 .../donations/templates/fulfillment.html           |   7 +
 talerdonations/donations/templates/index.html      |  62 ++++
 talerdonations/donations/templates/javascript.html |  22 ++
 talerdonations/helpers.py                          | 104 +++++++
 talerdonations/talerconfig.py                      | 321 +++++++++++++++++++++
 talerdonations/tests.conf                          |   0
 talerdonations/tests.py                            |   0
 16 files changed, 900 insertions(+), 5 deletions(-)
 create mode 100755 setup.py
 create mode 100644 talerdonations/__init__.py
 create mode 100644 talerdonations/donations/__init__.py
 create mode 100644 talerdonations/donations/donations.py
 create mode 100644 talerdonations/donations/templates/backoffice.html
 create mode 100644 talerdonations/donations/templates/checkout.html
 create mode 100644 talerdonations/donations/templates/execute-payment.html
 create mode 100644 talerdonations/donations/templates/fallback.html
 create mode 100644 talerdonations/donations/templates/fulfillment.html
 create mode 100644 talerdonations/donations/templates/index.html
 create mode 100644 talerdonations/donations/templates/javascript.html
 create mode 100644 talerdonations/helpers.py
 create mode 100644 talerdonations/talerconfig.py
 create mode 100644 talerdonations/tests.conf
 create mode 100644 talerdonations/tests.py

diff --git a/Makefile.in b/Makefile.in
index 8504506..dbe1651 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -4,14 +4,14 @@ INSTALL_DATA = $(INSTALL) -m 644
 prefix = @prefix@
 srcdir = @srcdir@
 
-script_templates = taler-merchant-blog frontend-blog.wsgi
+script_templates = taler-merchant-donations frontend-donations.wsgi
 templates = Makefile $(script_templates)
 
 edit = sed -e 's|@address@hidden|$(prefix)|g'
 
 .PHONY: all
 all: $(templates)
-       cd talerblog/blog/static/web-common && make && cd -
+       cd talerdonations/donations/static/web-common && make && cd -
 
 Makefile: Makefile.in
        ./config.status $@
@@ -24,7 +24,7 @@ $(script_templates): %: Makefile %.in
 
 .PHONY: install-data
 install-data: $(templates)
-       @$(INSTALL_DATA) -Dt $(prefix)/share/taler/ frontend-blog.wsgi
+       @$(INSTALL_DATA) -Dt $(prefix)/share/taler/ frontend-donations.wsgi
 
 #      @test -n "$$(ls -A talerbank/app/static/web-common/)" || \
 #      (echo "please check out git submodules"; exit 1)
@@ -43,10 +43,10 @@ install: $(templates) install-data
        @pip3 install . --install-option="address@hidden@"
        @# force update when sources changed
        @pip3 install . --install-option="address@hidden@" --upgrade --no-deps
-       cd talerblog/blog/static/web-common && make install && cd -
+       cd talerdonations/donations/static/web-common && make install && cd -
 
 # run testcases
 .PHONY: check
 check:
        @export address@hidden@/talersurvey/tests.conf; \
-        python3 talerblog/tests.py
+        python3 talerdonations/tests.py
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..c8d70c2
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+from setuptools import setup, find_packages
+
+setup(name='talerdonations',
+      version='0.0',
+      description='Example donations site for GNU Taler',
+      url='git://taler.net/donations',
+      author='Marcello Stanisci',
+      author_email='address@hidden',
+      license='GPL',
+      packages=find_packages(),
+      install_requires=["Flask>=0.10"],
+      package_data={
+          '':[
+              "survey/templates/*.html",
+              "survey/static/*.svg",
+              "survey/static/*.css",
+              "survey/static/*.js",
+              "survey/static/*.js.tar.gz",
+              "survey/static/web-common/*.png",
+              "survey/static/web-common/*.css",
+              "survey/static/web-common/*.js",
+              "survey/static/web-common/*.js.tar.gz",
+              "survey/static/web-common/*.html",
+      ]
+      },
+      scripts=['taler-merchant-donations'],
+      zip_safe=False)
diff --git a/talerdonations/__init__.py b/talerdonations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/talerdonations/donations/__init__.py 
b/talerdonations/donations/__init__.py
new file mode 100644
index 0000000..9ee2929
--- /dev/null
+++ b/talerdonations/donations/__init__.py
@@ -0,0 +1,3 @@
+from talerdonations.donations.donations import app
+
+__all__ = ["app"]
diff --git a/talerdonations/donations/donations.py 
b/talerdonations/donations/donations.py
new file mode 100644
index 0000000..4299c3c
--- /dev/null
+++ b/talerdonations/donations/donations.py
@@ -0,0 +1,212 @@
+# 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
+# GNU TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+#
+# @author Florian Dold
+# @author Marcello Stanisci
+
+import flask
+from urllib.parse import urljoin, urlencode, quote, parse_qsl
+import requests
+import logging
+import os
+import base64
+import random
+import time
+from datetime import datetime
+import jinja2
+from talerdonations.talerconfig import TalerConfig
+from talerdonations.helpers import (make_url,
+expect_parameter, amount_from_float, amount_to_float,
+join_urlparts, get_query_string, MissingParameterException,
+backend_error)
+
+logger = logging.getLogger(__name__)
+
+base_dir = os.path.dirname(os.path.abspath(__file__))
+
+app = flask.Flask(__name__, template_folder=base_dir)
+app.debug = True
+app.secret_key = base64.b64encode(os.urandom(64)).decode('utf-8')
+
+tc = TalerConfig.from_env()
+BACKEND_URL = tc["frontends"]["backend"].value_string(required=True)
+FRACTION = tc["frontends"]["fraction"].value_int(required=True)
+CURRENCY = tc["taler"]["currency"].value_string(required=True)
+MAX_FEE = dict(value=3, fraction=0, currency=CURRENCY)
+
+app.config.from_object(__name__)
+
address@hidden
+def utility_processor():
+    def url(my_url):
+        return join_urlparts(flask.request.script_root, my_url)
+    def env(name, default=None):
+        return os.environ.get(name, default)
+    return dict(url=url, env=env)
+
+
address@hidden("/")
+def index():
+    return flask.render_template("templates/index.html", 
merchant_currency=CURRENCY)
+
address@hidden("/javascript")
+def javascript_licensing():
+    return flask.render_template("templates/javascript.html")
+
+
address@hidden("/checkout", methods=["GET"])
+def checkout():
+    amount_str = expect_parameter("donation_amount")
+    donation_receiver = expect_parameter("donation_receiver")
+    try:
+        amount = float(amount_str)
+    except ValueError:
+        logger.warn("Invalid amount ('%s')", amount_str)
+        e = flask.jsonify(error="invalid amount")
+        return flask.make_response(e, 400)
+    display_alert = flask.request.args.get("display_alert", None)
+    return flask.render_template("templates/checkout.html",
+            donation_amount=amount_str,
+            donation_receiver=donation_receiver,
+            merchant_currency=CURRENCY,
+            display_alert=display_alert)
+
+
address@hidden("/generate-contract", methods=["GET"])
+def generate_contract():
+    try:
+        donation_receiver = expect_parameter("donation_receiver")
+        donation_amount = expect_parameter("donation_amount")
+    except MissingParameterException as e:
+        return flask.jsonify(dict(error="Missing parameter '%s'" % e)), 400
+    amount = amount_from_float(float(donation_amount))
+    order_id = "donation-%s-%X-%s" % \
+               (donation_receiver,
+               random.randint(0, 0xFFFFFFFF),
+               datetime.today().strftime('%H_%M_%S'))
+    order = dict(
+        summary="Donation!",
+        nonce=flask.request.args.get("nonce"),
+        amount=amount,
+        max_fee=dict(value=1, fraction=0, currency=CURRENCY),
+        order_id=order_id,
+        products=[
+            dict(
+                description="Donation to %s" % (donation_receiver,),
+                quantity=1,
+                product_id=0,
+                price=amount,
+            ),
+        ],
+        fulfillment_url=make_url("/fulfillment", ("order_id", order_id)),
+        pay_url=make_url("/pay"),
+        merchant=dict(
+            instance=donation_receiver,
+            address="nowhere",
+            name="Kudos Inc.",
+            jurisdiction="none",
+        ),
+    )
+    r = requests.post(urljoin(BACKEND_URL, 'proposal'), json=dict(order=order))
+    if 200 != r.status_code:
+        # It is important to use 'backend_error()', as it handles
+        # the case where the backend gives NO JSON as response.
+        # For example, if it dies, or nginx hijacks somehow the
+        # response.
+        return backend_error(r)
+    return flask.jsonify(r.json()), r.status_code
+
address@hidden("/donate")
+def donate():
+    donation_receiver = expect_parameter("donation_receiver")
+    donation_amount = expect_parameter("donation_amount")
+    payment_system = expect_parameter("payment_system")
+    if "taler" != payment_system:
+        return flask.redirect(make_url("checkout",
+                                       ("donation_receiver", 
donation_receiver),
+                                       ("donation_amount", donation_amount),
+                                       ("display_alert", True)))
+    response = 
flask.make_response(flask.render_template('templates/fallback.html'), 402)
+    response.headers["X-Taler-Contract-Url"] = \
+    make_url("/generate-contract",
+             ("donation_receiver", donation_receiver),
+             ("donation_amount", donation_amount))
+    return response
+
+
address@hidden("/fulfillment")
+def fulfillment():
+    order_id = expect_parameter("order_id")
+    payed_order_ids = flask.session.get("payed_order_ids", [])
+    print("order_id:", repr(order_id))
+    print("session:", repr(flask.session))
+    if order_id in payed_order_ids:
+        data = payed_order_ids[order_id]
+        return flask.render_template(
+                "templates/fulfillment.html",
+                donation_receiver=data["donation_receiver"],
+                donation_amount=data["donation_amount"],
+                order_id=order_id,
+                currency=CURRENCY)
+        
+    response = 
flask.make_response(flask.render_template("templates/fallback.html"), 402)
+    response.headers["X-Taler-Contract-Query"] = "fulfillment_url"
+    response.headers["X-Taler-Offer-Url"] = make_url("/")
+    return response
+
+
address@hidden("/pay", methods=["POST"])
+def pay():
+    deposit_permission = flask.request.get_json()
+    if deposit_permission is None:
+        e = flask.jsonify(error="no json in body")
+        return e, 400
+    r = requests.post(urljoin(BACKEND_URL, "pay"), json=deposit_permission)
+    if 200 != r.status_code:
+        return backend_error(r)
+    proposal_data = r.json()["contract_terms"]
+    order_id = proposal_data["order_id"]
+    payed_order_ids = flask.session["payed_order_ids"] = 
flask.session.get("payed_order_ids", {})
+    payed_order_ids[order_id] = dict(
+        donation_receiver=proposal_data["merchant"]["instance"],
+        donation_amount=amount_to_float(proposal_data["amount"])
+    )
+    print("received payment for", order_id)
+    return flask.jsonify(r.json()), r.status_code
+
address@hidden("/backoffice")
+def track():
+    response = 
flask.make_response(flask.render_template("templates/backoffice.html"))
+    return response
+
+
address@hidden("/history")
+def history():
+    qs = get_query_string().decode("utf-8")
+    url = urljoin(BACKEND_URL, "history")
+    r = requests.get(url, params=dict(parse_qsl(qs)))
+    if 200 != r.status_code:
+        return backend_error(r)
+    return flask.jsonify(r.json()), r.status_code
+
+
address@hidden("/track/order")
+def track_order():
+    instance = expect_parameter("instance")
+    order_id = expect_parameter("order_id")
+    url = urljoin(BACKEND_URL, "track/transaction")
+    r = requests.get(url, params=dict(order_id=order_id, instance=instance))
+    if 200 != r.status_code:
+        return backend_error(r)
+    return flask.jsonify(r.json()), r.status_code
diff --git a/talerdonations/donations/templates/backoffice.html 
b/talerdonations/donations/templates/backoffice.html
new file mode 100644
index 0000000..719d4b6
--- /dev/null
+++ b/talerdonations/donations/templates/backoffice.html
@@ -0,0 +1,46 @@
+{% extends "templates/base.html" %}
+{% block main %}
+  <h1>Backoffice</h1>
+  <p>This page simulates a backoffice facility.  Through it,
+  the user can see the money flow from Taler transactions to
+  wire transfers and viceversa.</p>
+  <table id="history" width="60%" style="visibility: hidden;">
+    <tbody>
+      <tr>
+        <th>Order ID</th>
+        <th>Summary</th>
+        <th>Amount</th>
+        <th>Date</th>
+      </tr>
+    </tbody>
+  </table>
+  <div class="loader"></div>
+  <div id="popup1" class="overlay">
+    <div class="popup">
+      <h2>
+        <a class="close" href="/backoffice">&times;</a>
+      </h2>
+      <div class="track-content">
+        <table>
+          <tbody>
+            <tr>
+              <th>WTID</th>
+              <th>Amount</th>
+              <th>Coin</th>
+              <th>Date</th>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+  <a href="#" onclick="get_history(true);" style="margin-left: 90%;">Fake 
scroll</a>
+{% endblock main %}
+
+{% block styles %}
+  <link rel="stylesheet" type="text/css" href="{{ url("/static/popup.css") }}">
+{% endblock styles %}
+
+{% block scripts %}
+  <script src="{{ url('/static/backoffice.js') }}" 
type="application/javascript"></script>
+{% endblock scripts %}
diff --git a/talerdonations/donations/templates/checkout.html 
b/talerdonations/donations/templates/checkout.html
new file mode 100644
index 0000000..a7d009c
--- /dev/null
+++ b/talerdonations/donations/templates/checkout.html
@@ -0,0 +1,56 @@
+{% extends "templates/base.html" %}
+
+{% block 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>
+  
+  <p {% if not display_alert %} style="display: none;" {% endif %} 
class="informational-fail">
+    Only Taler system available in this demo!
+  </p>
+
+  <form name="tform" action="{{ url('/donate') }}" method="get">
+    <div id="opt-form" align="left"><br>
+      <input type="hidden" name="donation_receiver" value="{{ 
donation_receiver }}"></input>
+      <input type="hidden" name="donation_amount" value="{{ donation_amount 
}}"></input>
+      <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 id="select-payment-method" type="submit" value="Ok"></input>
+    </div>
+  </form>
+
+</article>
+
+{% endblock main %}
diff --git a/talerdonations/donations/templates/execute-payment.html 
b/talerdonations/donations/templates/execute-payment.html
new file mode 100644
index 0000000..e1283ce
--- /dev/null
+++ b/talerdonations/donations/templates/execute-payment.html
@@ -0,0 +1,12 @@
+{% extends "templates/base.html" %}
+
+{% block scripts %}
+<meta name="contract-hash" value="{{ hc }}">
+<meta name="pay-url" value="{{ pay_url|safe }}">
+<meta name="offering-url" value="{{ offering_url|safe }}">
+<script src="static/execute-payment.js" type="application/javascript"></script>
+{% endblock scripts %}
+
+{% block main %}
+<div id="msg">Executing payment <span id="action-indicator"></span></div>
+{% endblock main %}
diff --git a/talerdonations/donations/templates/fallback.html 
b/talerdonations/donations/templates/fallback.html
new file mode 100644
index 0000000..448af2b
--- /dev/null
+++ b/talerdonations/donations/templates/fallback.html
@@ -0,0 +1,23 @@
+{% extends "templates/base.html" %}
+{% block main %}
+<div id="ccfakeform" class="fade">
+ <p>
+ Oops, it looks like you don't have a Taler wallet installed.  Why don't you 
enter
+ all your credit card details before reading the article? <em>You can also
+ use GNU Taler to complete the purchase at any time.</em>
+ </p>
+
+ <form>
+   First name<br> <input type="text"></input><br>
+   Family name<br> <input type="text"></input><br>
+   Age<br> <input type="text"></input><br>
+   Nationality<br> <input type="text"></input><br>
+   Gender<br> <input type="radio" name"gender">Male</input>
+   CC number<br> <input type="text"></input><br>
+   <input type="radio" name="gender">Female</input><br>
+ </form>
+ <form method="get" action="/cc-payment/{{ article_name }}">
+   <input type="submit"></input>
+ </form>
+</div>
+{% endblock main %}
diff --git a/talerdonations/donations/templates/fulfillment.html 
b/talerdonations/donations/templates/fulfillment.html
new file mode 100644
index 0000000..67e56ff
--- /dev/null
+++ b/talerdonations/donations/templates/fulfillment.html
@@ -0,0 +1,7 @@
+{% extends "templates/base.html" %}
+
+{% block main %}
+<p> Thanks for donating <strong>{{ donation_amount }} {{ currency }}</strong> 
to <strong>{{ donation_receiver }}</strong>.</p>
+
+<p>Please keep the order identifier <strong>{{ order_id }}</strong> as a 
receipt for your donation.</p>
+{% endblock main %}
diff --git a/talerdonations/donations/templates/index.html 
b/talerdonations/donations/templates/index.html
new file mode 100644
index 0000000..0dc72a4
--- /dev/null
+++ b/talerdonations/donations/templates/index.html
@@ -0,0 +1,62 @@
+{% extends "templates/base.html" %}
+
+{% block main %}
+<h1 lang="en">Welcome to the Taler Donation "Shop" Demo</h1>
+
+<p>This toy donations website shows the user experience for donations with 
Taler.
+You are paying with an imaginary currency ({{ merchant_currency }}).
+</p>
+
+<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="https://taler.net/wallet";>installation 
page</a> for other platforms.
+    </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 {{ merchant_currency }} you
+    wish to donate:</p>
+
+  <form name="tform" action="checkout" method="GET" class="pure-form">
+    <div class="participation" id="fake-shop">
+      <select name="donation_receiver">
+        <option value="GNUnet">GNUnet</option>
+        <option value="Taler">GNU Taler</option>
+        <option value="Tor">Tor</option>
+        <!-- options are added dynamically -->
+      </select>
+      <select id="taler-donation" name="donation_amount">
+        <option value="0.1">0.1 {{ merchant_currency }}</option>
+        <option value="1">1 {{ merchant_currency }}</option>
+        <option value="6">5 {{ merchant_currency }}</option>
+        <option value="10">10 {{ merchant_currency }}</option>
+      </select>
+      <input type="submit" class="pure-button pure-button-primary" 
value="Donate!"/>
+    </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>
+
+<h2>Back-office interface</h2>
+<!--p>Try the <a href="backoffice">back-office</a>, in order to track
+orders and corresponding deposits.
+</p Depends on #4992 -->
+
+{% endblock %}
diff --git a/talerdonations/donations/templates/javascript.html 
b/talerdonations/donations/templates/javascript.html
new file mode 100644
index 0000000..fa94218
--- /dev/null
+++ b/talerdonations/donations/templates/javascript.html
@@ -0,0 +1,22 @@
+<!-- This file is in the public domain -->
+<html>
+<body>
+<table id="jslicense-labels1">
+<tr>
+  <td><a 
href="/static/web-common/taler-wallet-lib.js">taler-wallet-lib.js</a></td>
+  <td><a href="https://www.gnu.org/licenses/lgpl-2.1.html";>LGPL</a></td>
+  <td><a 
href="/static/web-common/taler-wallet-lib.js.tar.gz">taler-wallet-lib.js.tar.gz</a></td>
+</tr>
+<tr>
+  <td><a 
href="/static/web-common/dropdown-navbar_script.js">dropdown-navbar_script.js</a></td>
+  <td><a href="https://www.gnu.org/licenses/lgpl-2.1.html";>LGPL</a></td>
+  <td><a 
href="/static/web-common/dropdown-navbar_script.js">dropdown-navbar_script.js</a></td>
+</tr>
+<tr>
+  <td><a href="/static/web-common/index.js">dropdown-navbar_script.js</a></td>
+  <td><a href="https://www.gnu.org/licenses/lgpl-2.1.html";>LGPL</a></td>
+  <td><a href="/static/web-common/index.js">dropdown-navbar_script.js</a></td>
+</tr>
+</table>
+</body>
+</html>
diff --git a/talerdonations/helpers.py b/talerdonations/helpers.py
new file mode 100644
index 0000000..257192d
--- /dev/null
+++ b/talerdonations/helpers.py
@@ -0,0 +1,104 @@
+#  This file is part of TALER
+#  (C) 2016 INRIA
+#
+#  TALER is free software; you can redistribute it and/or modify it under the
+#  terms of the GNU Affero General Public License as published by the Free 
Software
+#  Foundation; either version 3, 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License along with
+#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+#
+#  @author Florian Dold
+#  @author Marcello Stanisci
+
+from flask import request, jsonify, make_response, current_app, render_template
+import flask
+from urllib.parse import urljoin, urlencode
+import logging
+import requests
+import re
+import datetime
+import json
+from .talerconfig import TalerConfig
+
+logger = logging.getLogger(__name__)
+
+tc = TalerConfig.from_env()
+BACKEND_URL = tc["frontends"]["backend"].value_string(required=True)
+NDIGITS = tc["frontends"]["NDIGITS"].value_int()
+CURRENCY = tc["taler"]["CURRENCY"].value_string()
+
+FRACTION_BASE = 1e8
+
+if not NDIGITS:
+    NDIGITS = 2
+
+class MissingParameterException(Exception):
+    def __init__(self, param):
+        self.param = param
+
+def amount_to_float(amount):
+    return amount['value'] + (float(amount['fraction']) / float(FRACTION_BASE))
+
+
+def amount_from_float(x):
+    value = int(x)
+    fraction = int((x - value) * FRACTION_BASE)
+    return dict(currency=CURRENCY, value=value, fraction=fraction)
+
+
+def join_urlparts(*parts):
+    s = ""
+    i = 0
+    while i < len(parts):
+        n = parts[i]
+        i += 1
+        if s.endswith("/"):
+            n = n.lstrip("/")
+        elif s and  not n.startswith("/"):
+            n = "/" + n
+        s += n
+    return s
+
+
+def make_url(page, *query_params):
+    """
+    Return a URL to a page in the current Flask application with the given
+    query parameters (sequence of key/value pairs).
+    """
+    query = urlencode(query_params)
+    if page.startswith("/"):
+        root = request.url_root
+        page = page.lstrip("/")
+    else:
+        root = request.base_url
+    url = urljoin(root, "%s?%s" % (page, query))
+    # urlencode is overly eager with quoting, the wallet right now
+    # needs some characters unquoted.
+    return url.replace("%24", "$").replace("%7B", "{").replace("%7D", "}")
+
+
+def expect_parameter(name, alt=None):
+    value = request.args.get(name, None)
+    if value is None and alt is None:
+        logger.error("Missing parameter '%s' from '%s'." % (name, 
request.args))
+        raise MissingParameterException(name)
+    return value if value else alt
+
+
+def get_query_string():
+    return request.query_string
+
+def backend_error(requests_response):
+    logger.error("Backend error: status code: "
+                 + str(requests_response.status_code))
+    try:
+        return flask.jsonify(requests_response.json()), 
requests_response.status_code
+    except json.decoder.JSONDecodeError:
+        logger.error("Backend error (NO JSON returned): status code: "
+                     + str(requests_response.status_code))
+        return flask.jsonify(dict(error="Backend died, no JSON got from it")), 
502
diff --git a/talerdonations/talerconfig.py b/talerdonations/talerconfig.py
new file mode 100644
index 0000000..ba4dfbb
--- /dev/null
+++ b/talerdonations/talerconfig.py
@@ -0,0 +1,321 @@
+#  This file is part of TALER
+#  (C) 2016 INRIA
+#
+#  TALER is free software; you can redistribute it and/or modify it under the
+#  terms of the GNU Affero General Public License as published by the Free 
Software
+#  Foundation; either version 3, 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License along with
+#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+#
+#  @author Florian Dold
+
+"""
+Parse GNUnet-style configurations in pure Python
+"""
+
+# FIXME: make sure that autovivification of config entries
+# does not leave garbage behind (use weakrefs!)
+
+import logging
+import collections
+import os
+import weakref
+
+logger = logging.getLogger(__name__)
+
+__all__ = ["TalerConfig"]
+
+taler_datadir = None
+try:
+    # not clear if this is a good idea ...
+    from talerpaths import taler_datadir as t
+    taler_datadir = t
+except ImportError:
+    pass
+
+class ConfigurationError(Exception):
+    pass
+
+class ExpansionSyntaxError(Exception):
+    pass
+
+
+def expand(s, getter):
+    """
+    Do shell-style parameter expansion.
+    Supported syntax:
+    - ${X}
+    - ${X:-Y}
+    - $X
+    """
+    pos = 0
+    result = ""
+    while pos != -1:
+        start = s.find("$", pos)
+        if start == -1:
+            break
+        if s[start:].startswith("${"):
+            balance = 1
+            end = start + 2
+            while balance > 0 and end < len(s):
+                balance += {"{": 1, "}": -1}.get(s[end], 0)
+                end += 1
+            if balance != 0:
+                raise ExpansionSyntaxError("unbalanced parentheses")
+            piece = s[start+2:end-1]
+            if piece.find(":-") > 0:
+                varname, alt = piece.split(":-", 1)
+                replace = getter(varname)
+                if replace is None:
+                    replace = expand(alt, getter)
+            else:
+                varname = piece
+                replace = getter(varname)
+                if replace is None:
+                    replace = s[start:end]
+        else:
+            end = start + 2
+            while end < len(s) and s[start+1:end+1].isalnum():
+                end += 1
+            varname = s[start+1:end]
+            replace = getter(varname)
+            if replace is None:
+                replace = s[start:end]
+        result = result + replace
+        pos = end
+
+
+    return result + s[pos:]
+
+
+class OptionDict(collections.defaultdict):
+    def __init__(self, config, section_name):
+        self.config = weakref.ref(config)
+        self.section_name = section_name
+        super().__init__()
+    def __missing__(self, key):
+        e = Entry(self.config(), self.section_name, key)
+        self[key] = e
+        return e
+    def __getitem__(self, slice):
+        return super().__getitem__(slice.lower())
+    def __setitem__(self, slice, value):
+        super().__setitem__(slice.lower(), value)
+
+
+class SectionDict(collections.defaultdict):
+    def __init__(self):
+        super().__init__()
+    def __missing__(self, key):
+        v = OptionDict(self, key)
+        self[key] = v
+        return v
+    def __getitem__(self, slice):
+        return super().__getitem__(slice.lower())
+    def __setitem__(self, slice, value):
+        super().__setitem__(slice.lower(), value)
+
+
+class Entry:
+    def __init__(self, config, section, option, value=None, filename=None, 
lineno=None):
+        self.value = value
+        self.filename = filename
+        self.lineno = lineno
+        self.section = section
+        self.option = option
+        self.config = weakref.ref(config)
+
+    def __repr__(self):
+        return "<Entry section=%s, option=%s, value=%s>" % (self.section, 
self.option, repr(self.value),)
+
+    def __str__(self):
+        return self.value
+
+    def value_string(self, default=None, warn=False, required=False):
+        if required and self.value is None:
+            raise ConfigurationError("Missing required option '%s' in section 
'%s'" % (self.option.upper(), self.section.upper()))
+        if self.value is None:
+            if warn:
+                if default is not None:
+                    logger.warn("Configuration is missing option '%s' in 
section '%s', falling back to '%s'",
+                            self.option, self.section, default)
+                else:
+                    logger.warn("Configuration is missing option '%s' in 
section '%s'", self.option.upper(), self.section.upper())
+            return default
+        return self.value
+
+    def value_int(self, default=None, warn=False, required=False):
+        v = self.value_string(default, warn, required)
+        if v is None:
+            return None
+        try:
+            return int(v)
+        except ValueError:
+            raise ConfigurationError("Expected number for option '%s' in 
section '%s'" % (self.option.upper(), self.section.upper()))
+
+    def _getsubst(self, key):
+        x = self.config()["paths"][key].value
+        if x is not None:
+            return x
+        x = os.environ.get(key)
+        if x is not None:
+            return x
+        return None
+
+    def value_filename(self, default=None, warn=False, required=False):
+        v = self.value_string(default, warn, required)
+        if v is None:
+            return None
+        return expand(v, lambda x: self._getsubst(x))
+
+    def location(self):
+        if self.filename is None or self.lineno is None:
+            return "<unknown>"
+        return "%s:%s" % (self.filename, self.lineno)
+
+
+class TalerConfig:
+    """
+    One loaded taler configuration, including base configuration
+    files and included files.
+    """
+    def __init__(self):
+        """
+        Initialize an empty configuration
+        """
+        self.sections = SectionDict()
+
+    @staticmethod
+    def from_file(filename=None, load_defaults=True):
+        cfg = TalerConfig()
+        if filename is None:
+            xdg = os.environ.get("XDG_CONFIG_HOME")
+            if xdg:
+                filename = os.path.join(xdg, "taler.conf")
+            else:
+                filename = os.path.expanduser("~/.config/taler.conf")
+        if load_defaults:
+            cfg.load_defaults()
+        cfg.load_file(filename)
+        return cfg
+
+    def value_string(self, section, option, default=None, required=None, 
warn=False):
+        return self.sections[section][option].value_string(default, required, 
warn)
+
+    def value_filename(self, section, option, default=None, required=None, 
warn=False):
+        return self.sections[section][option].value_filename(default, 
required, warn)
+
+    def value_int(self, section, option, default=None, required=None, 
warn=False):
+        return self.sections[section][option].value_int(default, required, 
warn)
+
+    def load_defaults(self):
+        base_dir = os.environ.get("TALER_BASE_CONFIG")
+        if base_dir:
+            self.load_dir(base_dir)
+            return
+        prefix = os.environ.get("TALER_PREFIX")
+        if prefix:
+            self.load_dir(os.path.join(prefix, "share/taler/config.d"))
+            return
+        if taler_datadir:
+            self.load_dir(os.path.join(taler_datadir, "share/taler/config.d"))
+            return
+        logger.warn("no base directory found")
+
+    @staticmethod
+    def from_env(*args, **kwargs):
+        """
+        Load configuration from environment variable TALER_CONFIG_FILE
+        or from default location if the variable is not set.
+        """
+        filename = os.environ.get("TALER_CONFIG_FILE")
+        return TalerConfig.from_file(filename, *args, **kwargs)
+
+    def load_dir(self, dirname):
+        try:
+            files = os.listdir(dirname)
+        except FileNotFoundError:
+            logger.warn("can't read config directory '%s'", dirname)
+            return
+        for file in files:
+            if not file.endswith(".conf"):
+                continue
+            self.load_file(os.path.join(dirname, file))
+
+    def load_file(self, filename):
+        sections = self.sections
+        with open(filename, "r") as file:
+            lineno = 0
+            current_section = None
+            for line in file:
+                lineno += 1
+                line = line.strip()
+                if len(line) == 0:
+                    # empty line
+                    continue
+                if line.startswith("#"):
+                    # comment
+                    continue
+                if line.startswith("["):
+                    if not line.endswith("]"):
+                        logger.error("invalid section header in line %s: %s", 
lineno, repr(line))
+                    section_name = line.strip("[]").strip().strip('"')
+                    current_section = section_name
+                    continue
+                if current_section is None:
+                    logger.error("option outside of section in line %s: %s", 
lineno, repr(line))
+                    continue
+                kv = line.split("=", 1)
+                if len(kv) != 2:
+                    logger.error("invalid option in line %s: %s", lineno, 
repr(line))
+                key = kv[0].strip()
+                value = kv[1].strip()
+                if value.startswith('"'):
+                    value = value[1:]
+                    if not value.endswith('"'):
+                        logger.error("mismatched quotes in line %s: %s", 
lineno, repr(line))
+                    else:
+                        value = value[:-1]
+                e = Entry(self.sections, current_section, key, value, 
filename, lineno)
+                sections[current_section][key] = e
+
+
+    def dump(self):
+        for section_name, section in self.sections.items():
+            print("[%s]" % (section.section_name,))
+            for option_name, e in section.items():
+                print("%s = %s # %s" % (e.option, e.value, e.location()))
+
+    def __getitem__(self, slice):
+        if isinstance(slice, str):
+            return self.sections[slice]
+        raise TypeError("index must be string")
+
+
+if __name__ == "__main__":
+    import sys
+    import argparse
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--section", "-s", dest="section", default=None, 
metavar="SECTION")
+    parser.add_argument("--option", "-o", dest="option", default=None, 
metavar="OPTION")
+    parser.add_argument("--config", "-c", dest="config", default=None, 
metavar="FILE")
+    parser.add_argument("--filename", "-f", dest="expand_filename", 
default=False, action='store_true')
+    args = parser.parse_args()
+
+    tc = TalerConfig.from_file(args.config)
+
+    if args.section is not None and args.option is not None:
+        if args.expand_filename:
+            x = tc.value_filename(args.section, args.option)
+        else:
+            x = tc.value_string(args.section, args.option)
+        if x is not None:
+            print(x)
+    else:
+        tc.dump()
diff --git a/talerdonations/tests.conf b/talerdonations/tests.conf
new file mode 100644
index 0000000..e69de29
diff --git a/talerdonations/tests.py b/talerdonations/tests.py
new file mode 100644
index 0000000..e69de29

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]