summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile15
-rw-r--r--README-draft46
-rw-r--r--README.md54
-rwxr-xr-xbin/taler-merchant-demos19
-rwxr-xr-xsetup.py1
-rw-r--r--talermerchantdemos/blog/blog.py190
-rw-r--r--talermerchantdemos/blog/templates/article_frame.html11
-rw-r--r--talermerchantdemos/blog/templates/article_frame.html.j215
-rw-r--r--talermerchantdemos/blog/templates/article_refunded.html10
-rw-r--r--talermerchantdemos/blog/templates/article_refunded.html.j216
-rw-r--r--talermerchantdemos/blog/templates/base.html.j2 (renamed from talermerchantdemos/blog/templates/base.html)47
-rw-r--r--talermerchantdemos/blog/templates/confirm_refund.html19
-rw-r--r--talermerchantdemos/blog/templates/confirm_refund.html.j222
-rw-r--r--talermerchantdemos/blog/templates/error.html22
-rw-r--r--talermerchantdemos/blog/templates/error.html.j224
-rw-r--r--talermerchantdemos/blog/templates/index.html41
-rw-r--r--talermerchantdemos/blog/templates/show_refund.html28
-rw-r--r--talermerchantdemos/donations/donations.py7
-rw-r--r--talermerchantdemos/httpcommon/__init__.py7
-rw-r--r--talermerchantdemos/survey/survey.py7
21 files changed, 395 insertions, 207 deletions
diff --git a/.gitignore b/.gitignore
index 8e95770..92ed544 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
talermerchantdemos/static/*.css
-
talermerchantdemos/static/navbar.css.map
diff --git a/Makefile b/Makefile
index eefac0d..0cea1c3 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,21 @@ dist:
pretty:
yapf -r -i talerblog/
+# i18n
+extract:
+# Note: Flask-BabelEx expects 'translations/' for the dirname,
+# even though GNU gettext typically uses 'locale/'
+ mkdir -p translations/
+ pybabel extract -F babel.cfg -o translations/messages.pot .
+# Add new language as follows:
+# pybabel init -i locale/messages.pot -d locale/ -l de
+
+compile:
+ pybabel compile -d translations/
+
+update: extract
+ pybabel update -i translations/messages.pot -d translations/
+
# SASS/SCSS
sass-setup:
diff --git a/README-draft b/README-draft
new file mode 100644
index 0000000..e3d7a95
--- /dev/null
+++ b/README-draft
@@ -0,0 +1,46 @@
+Configuring the Blog
+====================
+
+To serve the blog directly via HTTP:
+------------------------------------
+
+[frontends]
+backend_apikey = "ApiKey Sandbox"
+backend = https://backend.test.taler.net/
+
+[taler]
+currency = TESTKUDOS
+
+[blog]
+http_port = 8080
+serve = http
+
+
+To serve the blog via UWSGI over TCP:
+-------------------------------------
+[frontends]
+backend_apikey = "ApiKey Sandbox"
+backend = https://backend.test.taler.net/
+
+[taler]
+currency = TESTKUDOS
+
+[blog]
+serve = uwsgi
+uwsgi_serve = tcp
+uwsgi_port = XYZ
+
+To serve the blog via UWSGI over UNIX domain socket:
+----------------------------------------------------
+[frontends]
+backend_apikey = "ApiKey Sandbox"
+backend = https://backend.test.taler.net/
+
+[taler]
+currency = TESTKUDOS
+
+[blog]
+serve = uwsgi
+uwsgi_serve = unix
+uwsgi_unixpath = "/tmp/blog.uwsgi"
+uwsgi_unixpath_mode = XZY
diff --git a/README.md b/README.md
index d221946..c9f3e7f 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,9 @@ Step 1: `cd` into the directory:<br/>
> ```$ cd taler-merchant-demos```
<br/>
-Step 2: Ensure Python3.5 or above is installed using a command like:<br/>
+Step 2: Ensure Python 3.5 or above and Flask with the Babel extension are installed using a command like:<br/>
-> ```$ sudo apt install python3.8 -y```
+> ```$ sudo apt install python3.8 python3-flask-babel -y```
<br/>
Step 3: Ensure Python3 Pip is installed:<br/>
@@ -20,7 +20,7 @@ Step 3: Ensure Python3 Pip is installed:<br/>
> ```$ sudo apt install python3-pip -y```
<br/>
-Step 4: configure it using:<br/>
+Step 4: configure this package using:<br/>
> ```$ ./configure --destination=local```
@@ -46,6 +46,10 @@ Step 7: Install NPM<br>
Step 8: Install scss<br>
> ```$ make sass-install```
+This will the NPM implementation of sass. If you have a Ruby
+tool called 'sass' installed, this build will NOT work.
+
+
## Quick Install for the dependencies
Here's one command to automatically install all dependencies at once:
> ```$ sudo apt install python3.8 python3-pip npm -y; pip3 install lxml uwsgi; ./configure --destination=local; sudo npm install -g npm node;make sass-install```
@@ -60,17 +64,19 @@ Step 1: Open the config:
Step 2: Adding the required keys:
> Copy & Paste the following into the file:
+
> ```
> [frontends]
> backend_apikey = "ApiKey Sandbox"
> backend = https://backend.test.taler.net/
->
+>
> [taler]
> currency = TESTKUDOS
->
+>
> [blog]
> http_port = 8080
-> ```
+> serve = http
+```
<br>
Step 3: Configure the config:
@@ -91,4 +97,38 @@ To apply ***SCSS*** changes, use
## Running the program
To start the server, use the following command:<br>
-> ```$ taler-merchant-demos --serve-http blog```
+> ```$ taler-merchant-demos blog```
+
+## More configuration options.
+This makes the blog speak UWSGI over TCP:
+> ```
+> [frontends]
+> backend_apikey = "ApiKey Sandbox"
+> backend = https://backend.test.taler.net/
+>
+> [taler]
+> currency = TESTKUDOS
+>
+> [blog]
+> serve = uwsgi
+> uwsgi_serve = tcp
+> uwsgi_port = XZY
+
+> ```
+<br>
+
+This makes the blog speak UWSGI over unix domain socket:
+> ```
+> [frontends]
+> backend_apikey = "ApiKey Sandbox"
+> backend = https://backend.test.taler.net/
+>
+> [taler]
+> currency = TESTKUDOS
+>
+> [blog]
+> serve = uwsgi
+> uwsgi_serve = unix
+> uwsgi_unixpath = "/tmp/blog.uwsgi"
+> uwsgi_unixpath_mode = XZY
+> ```
diff --git a/bin/taler-merchant-demos b/bin/taler-merchant-demos
index 7ed69d7..f2318bf 100755
--- a/bin/taler-merchant-demos
+++ b/bin/taler-merchant-demos
@@ -90,22 +90,25 @@ def handle_serve_http(config, whichShop, port=None):
sys.stderr.write("Failed to start uwsgi. Please make sure to install uwsgi for Python3.")
sys.exit(1)
+def handle_serve_from_config(config_obj, which_shop):
+ if config_obj.value_string("blog", "serve", required=True).lower() == "http":
+ http_port = config_obj.value_int(which_shop, "port")
+ return handle_serve_http(config_obj, which_shop, http_port)
+ handle_serve_uwsgi(config_obj, which_shop)
+
@click.command("Global shop launcher")
@click.option("--config", help="Configuration file", required=False)
-@click.option("--serve-http", help="Serve via HTTP", is_flag=True, required=False)
-@click.option("--port", help="HTTP port to serve (if not given, it's picked from config)", required=False, type=int)
-@click.option("--serve-uwsgi", help="Serve via UWSGI (default)", required=False)
+@click.option("--http-port", help="HTTP port to serve (if not given, serving comes from config)", required=False, type=int)
@click.argument("which-shop")
-def demos(config, serve_http, port, serve_uwsgi, which_shop):
+def demos(config, http_port, which_shop):
"""
WHICH_SHOP is one of: blog, donations, or survey.
"""
if config:
os.environ["TALER_CONFIG_FILE"] = config
config_obj = TalerConfig.from_file(os.environ.get("TALER_CONFIG_FILE"))
- if serve_http: # port was given
- handle_serve_http(config_obj, which_shop, port)
- else:
- handle_serve_uwsgi(config_obj, which_shop)
+ if http_port:
+ return handle_serve_http(config_obj, which_shop, http_port)
+ handle_serve_from_config(config_obj, which_shop)
demos()
diff --git a/setup.py b/setup.py
index deeba73..af37dc7 100755
--- a/setup.py
+++ b/setup.py
@@ -20,6 +20,7 @@ setup(name='talermerchantdemos',
"static/*.svg",
# Blog files
"blog/templates/*.html",
+ "blog/templates/*.j2",
"blog/articles/*",
"blog/data/*",
# Donation files
diff --git a/talermerchantdemos/blog/blog.py b/talermerchantdemos/blog/blog.py
index 865605b..c8315e5 100644
--- a/talermerchantdemos/blog/blog.py
+++ b/talermerchantdemos/blog/blog.py
@@ -1,6 +1,6 @@
##
-# This file is part of GNU taler.
-# Copyright (C) 2014-2017 INRIA
+# This file is part of GNU Taler.
+# Copyright (C) 2014-2020 Taler Systems SA
#
# 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
@@ -24,21 +24,57 @@ import traceback
import uuid
import base64
import flask
+from flask import request
+from flask_babel import Babel
+from flask_babel import refresh
+from flask_babel import force_locale
+from flask_babel import gettext
import time
import sys
from urllib.parse import urljoin, urlencode, urlparse
from taler.util.talerconfig import TalerConfig, ConfigurationError
from ..blog.content import ARTICLES, get_article_file, get_image_file
-from talermerchantdemos.httpcommon import backend_get, backend_post
+from talermerchantdemos.httpcommon import backend_get, backend_post, fallback_404
+from datetime import datetime
+
+class Deadline:
+ def __init__(self, value):
+ self.value = value
+ def isExpired(self):
+ if self.value == "never":
+ return False
+ now = int(round(time.time()) * 1000)
+ now_dt = datetime.fromtimestamp(now / 1000)
+ deadline_dt = datetime.fromtimestamp(self.value / 1000)
+ print("debug: checking refund expiration, now: {}, deadline: {}".format(
+ now_dt.strftime("%c"), deadline_dt.strftime("%c")
+ ))
+ return now > self.value
+
+def refundable(pay_status):
+ refunded = pay_status.get("refunded")
+ refund_deadline = pay_status.get("contract_terms", {}).get("refund_deadline")
+ assert(refunded != None and refund_deadline)
+ t_ms = refund_deadline.get("t_ms")
+ assert(t_ms)
+ rd = Deadline(t_ms)
+ if not refunded and not rd.isExpired():
+ return True
+ return False
if not sys.version_info.major == 3 and sys.version_info.minor >= 6:
print("Python 3.6 or higher is required.")
- print("You are using Python {}.{}.".format(sys.version_info.major, sys.version_info.minor))
+ print(
+ "You are using Python {}.{}.".format(
+ sys.version_info.major, sys.version_info.minor
+ )
+ )
sys.exit(1)
-
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
-app = flask.Flask(__name__, template_folder=BASE_DIR, static_folder=BASE_DIR + '/../static/')
+app = flask.Flask(
+ __name__, template_folder=BASE_DIR, static_folder=BASE_DIR + "/../static/"
+)
app.secret_key = base64.b64encode(os.urandom(64)).decode("utf-8")
LOGGER = logging.getLogger(__name__)
@@ -54,6 +90,34 @@ ARTICLE_AMOUNT = CURRENCY + ":0.5"
BACKEND_URL = urljoin(BACKEND_BASE_URL, "instances/blog/")
app.config.from_object(__name__)
+babel = Babel(app)
+
+print("Using translations from:")
+print(list(babel.translation_directories))
+translations = [str(translation) for translation in babel.list_translations()]
+translations.append('en')
+print("Operating with the following translations available:")
+print(translations)
+
+
+##
+# Helper function used inside Jinja2 logic to create a links
+# to the current page but in a different language. Used to
+# implement the "Language" menu.
+#
+def self_localized(lang):
+ """
+ Return URL for the current page in another locale.
+ """
+ path = request.path
+ # path must have the form "/$LANG/$STUFF"
+ parts = path.split('/', 2)
+ if (2 >= len(parts)):
+ # Totally unexpected path format, do not localize
+ return path
+ return "/" + lang + "/" + parts[2]
+
+app.jinja_env.globals.update(self_localized=self_localized)
##
@@ -76,7 +140,7 @@ def utility_processor():
# @param abort_status_code status code to return along the response.
# @param params _kw_ arguments to passed verbatim to the templating engine.
def err_abort(abort_status_code, **params):
- t = flask.render_template("templates/error.html", **params)
+ t = flask.render_template("templates/error.html.j2", **params)
flask.abort(flask.make_response(t, abort_status_code))
@@ -89,7 +153,7 @@ def err_abort(abort_status_code, **params):
@app.errorhandler(Exception)
def internal_error(e):
return flask.render_template(
- "templates/error.html", message="Internal error", stack=traceback.format_exc()
+ "templates/error.html.j2", message=gettext("Internal error"), stack=traceback.format_exc()
)
@@ -99,11 +163,21 @@ def internal_error(e):
# @return response object of the index page.
@app.route("/")
def index():
- supported = ['en', 'de' ]
default = 'en'
- target = flask.request.accept_languages.best_match(supported, default)
+ target = flask.request.accept_languages.best_match(translations, default)
return flask.redirect("/" + target + "/", code=302)
+@babel.localeselector
+def get_locale():
+ parts = request.path.split('/', 2)
+ if (2 >= len(parts)):
+ # Totally unexpected path format, do not localize
+ return "en"
+ lang = parts[1]
+ if lang in translations:
+ return lang
+ return "en"
+
##
# Serve the main index page for a particular language.
#
@@ -111,7 +185,7 @@ def index():
@app.route("/<lang>/")
def start(lang):
return flask.render_template(
- "templates/index.html", lang=lang, merchant_currency=CURRENCY, articles=ARTICLES.values()
+ "templates/index.html.j2", lang=lang, merchant_currency=CURRENCY, articles=ARTICLES.values()
)
@@ -127,8 +201,13 @@ def confirm_refund(lang, order_id):
400, message="Cannot refund unpaid article",
)
article_name = pay_status["contract_terms"]["extra"]["article_name"]
+
+ if not refundable(pay_status):
+ return flask.render_template(
+ "templates/error.html.j2", message=gettext("Article is not anymore refundable")
+ )
return flask.render_template(
- "templates/confirm_refund.html", article_name=article_name, order_id=order_id
+ "templates/confirm_refund.html.j2", article_name=article_name, order_id=order_id
)
@@ -152,10 +231,15 @@ def refund(lang, order_id):
BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id)
)
order_status = pay_status.get("order_status")
+
if order_status != "paid":
err_abort(
402, message="You did not pay for this article (nice try!)", json=pay_status
)
+ if not refundable(pay_status):
+ err_abort(
+ 403, message="Item not refundable (anymore)", json=pay_status
+ )
refund_spec = dict(reason="Demo reimbursement", refund=ARTICLE_AMOUNT)
resp = backend_post(BACKEND_URL, f"private/orders/{order_id}/refund", refund_spec)
return flask.redirect(pay_status["order_status_url"])
@@ -174,7 +258,7 @@ def refund(lang, order_id):
# - 404: supplemental @a data not found.
# In the successful case, a response object carrying the
# article in it will be returned.
-def render_article(article_name, data, order_id):
+def render_article(article_name, data, order_id, refundable):
article_info = ARTICLES.get(article_name)
if article_info is None:
m = "Internal error: Files for article ({}) not found.".format(article_name)
@@ -188,10 +272,11 @@ def render_article(article_name, data, order_id):
err_abort(404, message=m)
# the order_id is needed for refunds
return flask.render_template(
- "templates/article_frame.html",
+ "templates/article_frame.html.j2",
article_file=get_article_file(article_info),
article_name=article_name,
order_id=order_id,
+ refundable=refundable
)
##
@@ -207,6 +292,27 @@ def post_order(article_name,lang):
fulfillment_url=flask.request.base_url,
summary="Essay: " + article_name.replace("_", " "),
# 10 minutes time for a refund
+ wire_transfer_deadline=dict(t_ms=1000 * int(time.time() + 15 * 30)),
+ )
+ order_resp = backend_post(
+ BACKEND_URL,
+ "private/orders",
+ dict(order=order, refund_delay=dict(d_ms=1000 * 120)))
+ return order_resp
+
+##
+# Setup a fresh order with the backend.
+#
+# @param article_name which article the order is for
+# @param lang which language to use
+#
+def post_order(article_name,lang):
+ order = dict(
+ amount=ARTICLE_AMOUNT,
+ extra=dict(article_name=article_name,lang=lang),
+ fulfillment_url=flask.request.base_url,
+ summary="Essay: " + article_name.replace("_", " "),
+ # 10 minutes time for a refund
refund_deadline=dict(t_ms=1000 * int(time.time() + 10 * 30)),
wire_transfer_deadline=dict(t_ms=1000 * int(time.time() + 15 * 30)),
)
@@ -258,7 +364,6 @@ def article(article_name, lang=None, data=None):
pay_status = backend_get(
BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id)
)
-
order_status = pay_status.get("order_status")
if order_status == "claimed":
if not lang:
@@ -274,30 +379,49 @@ def article(article_name, lang=None, data=None):
if order_status == "paid":
refunded = pay_status["refunded"]
- if refunded:
+ if refunded:
return flask.render_template(
- "templates/article_refunded.html",
+ "templates/article_refunded.html.j2",
article_name=article_name,
order_id=order_id,
)
- else:
- response = render_article(article_name, data, order_id)
- response.set_cookie("order_id", order_id, path=urllib.parse.quote(f"/essay/{article_name}"))
- response.set_cookie("order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}"))
- return response
- else:
- # Check if the customer came on this page via the
- # re-purchase detection mechanism
- ai = pay_status.get("already_paid_order_id")
- au = pay_status.get("already_paid_fulfillment_url")
- if ai is not None and au is not None:
- response = flask.redirect(au)
- response.set_cookie("order_id", ai, path=urllib.parse.quote(f"/essay/{article_name}"))
- return response
+ response = render_article(article_name, data, order_id, refundable(pay_status))
+ response.set_cookie(
+ "order_id", order_id, path=urllib.parse.quote(f"/essay/{article_name}")
+ )
+ response.set_cookie(
+ "order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
+ )
+ return response
+
+ # Check if the customer came on this page via the
+ # re-purchase detection mechanism
+ ai = pay_status.get("already_paid_order_id")
+ au = pay_status.get("already_paid_fulfillment_url")
+ if ai is not None and au is not None:
+ response = flask.redirect(au)
+ response.set_cookie(
+ "order_id", ai, path=urllib.parse.quote(f"/essay/{article_name}")
+ )
+ return response
# Redirect the browser to a page where the wallet can
# run the payment protocol.
response = flask.redirect(pay_status["order_status_url"])
- response.set_cookie("order_id", order_id, path=urllib.parse.quote(f"/essay/{article_name}"))
- response.set_cookie("order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}"))
+ response.set_cookie(
+ "order_id", order_id, path=urllib.parse.quote(f"/essay/{article_name}")
+ )
+ response.set_cookie(
+ "order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
+ )
return response
+
+@app.errorhandler(500)
+def handler(e):
+ return flask.render_template(
+ "templates/error.html.j2", message=gettext("Internal server error"))
+
+@app.errorhandler(404)
+def handler(e):
+ return flask.render_template(
+ "templates/error.html.j2", message=gettext("Page not found"))
diff --git a/talermerchantdemos/blog/templates/article_frame.html b/talermerchantdemos/blog/templates/article_frame.html
deleted file mode 100644
index 8c2b6d4..0000000
--- a/talermerchantdemos/blog/templates/article_frame.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
-{% include "articles/" + article_file %}
-
-<hr>
-<p>
- You did not like this article?
- <a href="{{ url_for('confirm_refund', lang='en', order_id=order_id) }}">Get a refund</a>
- within the first hour after buying it.
-</p>
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/article_frame.html.j2 b/talermerchantdemos/blog/templates/article_frame.html.j2
new file mode 100644
index 0000000..a878e95
--- /dev/null
+++ b/talermerchantdemos/blog/templates/article_frame.html.j2
@@ -0,0 +1,15 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+{% include "articles/" + article_file %}
+
+{% if refundable %}
+<hr>
+<p>
+ {{
+ gettext("You did not like this article?") +
+ gettext("You can <a href="{url}">request a refund</a> within the first hour after buying it.").format(url=url_for('confirm_refund', lang='en', order_id=order_id)
+ }}
+</p>
+{% endif %}
+
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/article_refunded.html b/talermerchantdemos/blog/templates/article_refunded.html
deleted file mode 100644
index 95c4a6b..0000000
--- a/talermerchantdemos/blog/templates/article_refunded.html
+++ /dev/null
@@ -1,10 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
-
-<h2>Refunded</h2>
-
-<p>Your payment (order ID <tt>{{ order_id }}<tt>) for the article "{{ article_name }}" has been refunded.</p>
-
-<p>You won't be able to view it until you pay for it again.</p>
-
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/article_refunded.html.j2 b/talermerchantdemos/blog/templates/article_refunded.html.j2
new file mode 100644
index 0000000..34e0a0b
--- /dev/null
+++ b/talermerchantdemos/blog/templates/article_refunded.html.j2
@@ -0,0 +1,16 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+
+<h2>{{ gettext("Refunded") }}</h2>
+
+<p>
+{{
+ gettext("Your payment (order ID <tt>{order}<tt>) for the article "{article}" has been refunded.").format(order=order_id,article=article_name)
+}}
+</p>
+
+<p>
+{{ gettext("You won't be able to view it until you pay for it again.") }}
+</p>
+
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/base.html b/talermerchantdemos/blog/templates/base.html.j2
index 824cf9d..58ce857 100644
--- a/talermerchantdemos/blog/templates/base.html
+++ b/talermerchantdemos/blog/templates/base.html.j2
@@ -1,7 +1,7 @@
<!DOCTYPE html>
-<!--
+<!--
This file is part of GNU TALER.
- Copyright (C) 2014, 2015, 2016 INRIA
+ Copyright (C) 2014, 2015, 2016, 2020 Taler Systems SA
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
@@ -20,7 +20,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block meta %}{% endblock %}
- <title>Taler Essay Shop Demo</title>
+ <title>{{ gettext("Taler Essay Shop Demo") }}</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='pure.css') }}" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='demo.css') }}" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='navbar.css') }}" />
@@ -62,7 +62,7 @@
color: black;
}
</style>
-
+
{% block styles %}{% endblock %}
{% block scripts %}{% endblock %}
</head>
@@ -71,33 +71,33 @@
<header class="demobar" style="display: flex; flex-direction: column;">
<h1><span class="tt adorn-brackets">Taler Demo</span></h1>
<h1><span class="it"><a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG') }}">Shop</a></span></h1>
- <p>On this page you can buy articles using an imaginary currency (for now).
- The articles are chapters from Richard Stallman's book &quot;Free Software, Free Society&quot;,
- which is also
- <a href="http://shop.fsf.org/product/free-software-free-society-2/">published by the FSF</a>
- and available gratis at <a href="http://www.gnu.org/">gnu.org</a>.
+ <p>{{
+ gettext("On this page you can buy articles using an imaginary currency.") + "<br>" +
+ gettext("The articles are chapters from Richard Stallman's book &quot;Free Software, Free Society&quot;.") + "<br>" +
+ gettext('The book is <a href="{shop}">published by the FSF</a> and available gratis at <a href="{gnu}">gnu.org</a>.').format(shop="https://shop.fsf.org/product/free-software-free-society-2", gnu="https://www.gnu.org")
+ }}
</p>
</header>
<div style="display:flex; flex-direction: column;" class="navcontainer">
<nav class="demolist">
- <a href="{{ env('TALER_ENV_URL_INTRO', '#') }}">Introduction</a>
- <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">Bank</a>
- <a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}" class="active">Essay Shop</a>
- <a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#') }}">Donations</a>
- <a href="{{ env('TALER_ENV_URL_MERCHANT_SURVEY', '#') }}">Tipping/Survey</a>
- <a href="{{ env('TALER_ENV_URL_BACKOFFICE', '#') }}">Back-office</a>
+ <a href="{{ env('TALER_ENV_URL_INTRO', '#') }}">{{gettext("Introduction")}}</a>
+ <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">{{gettext("Bank")}}</a>
+ <a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}" class="active">{{gettext("Essay Shop")}}</a>
+ <a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#') }}">{{gettext("Donations")}}</a>
+ <a href="{{ env('TALER_ENV_URL_MERCHANT_SURVEY', '#') }}">{{gettext("Tipping/Survey")}}</a>
+ <!-- a href="{{ env('TALER_ENV_URL_BACKOFFICE', '#') }}">{{gettext("Back-office")}}</a -->
<span class="right">
- Language
+ {{ gettext("English [en]") }}
<!-- <input type="checkbox"> -->
<div class="nav">
<br>
<!--<hr style="width: 100%;">-->
- <a href="{{ '#LANG_EN_LANGEND' }}" class="navbtn">EN</a><br>
- <a href="{{ '#LANG_DE_LANGEND' }}" class="navbtn">DE</a><br>
- <a href="{{ '#LANG_FR_LANGEND' }}" class="navbtn">FR</a><br>
- <a href="{{ '#LANG_IT_LANGEND' }}" class="navbtn">IT</a><br>
- <a href="{{ '#LANG_JP_LANGEND' }}" class="navbtn">JP</a>
- <!-- lang strings structured as such to make replacing them with code using an ide's replace function easy -->
+ {% if lang != 'en' %}
+ <a href="{{ self_localized('en') }}" class="navbtn">English [en]</a><br>
+ {% endif %}
+ {% if lang != 'de' %}
+ <a href="{{ self_localized('de') }}" class="navbtn">Deutsch [de]</a><br>
+ {% endif %}
</div>
</span>
</nav>
@@ -110,11 +110,10 @@
{% endblock %}
<hr />
<div>
- <p>You can learn more about Taler on our main <a href="https://taler.net">website</a>.</p>
+ <p>{{ gettext('You can learn more about Taler on our main <a href="{site}">website</a>.').format(site="https://taler.net/") }}</p>
<div style="flex-grow:1"></div>
<p>Copyright &copy; 2014&mdash;2020 Taler Systems SA</p>
</div>
</section>
</body>
</html>
-
diff --git a/talermerchantdemos/blog/templates/confirm_refund.html b/talermerchantdemos/blog/templates/confirm_refund.html
deleted file mode 100644
index c4773d3..0000000
--- a/talermerchantdemos/blog/templates/confirm_refund.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
- <h1>Refund Article?</h1>
-
- <p>
- Do you want to get a refund for the article <em>{{ article_name }}</em>? After you have requested a refund,
- you won't be able to read the article anymore.
- </p>
-
- <p>
- You will only be able to receive the refund on the same wallet that you have used to pay
- for this article originally.
- </p>
-
- <form action="{{ url_for('refund', order_id=order_id) }}" method="POST">
- <input type="text" name="article_name" value={{ article_name}} hidden>
- <input type="submit" value="Request refund">
- </form>
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/confirm_refund.html.j2 b/talermerchantdemos/blog/templates/confirm_refund.html.j2
new file mode 100644
index 0000000..09f3730
--- /dev/null
+++ b/talermerchantdemos/blog/templates/confirm_refund.html.j2
@@ -0,0 +1,22 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+ <h1>{{ gettext("Confirm refund request for article"))</h1>
+
+ <p>
+ {{
+ gettext("Do you want to get a refund for the article <em>{name}</em>?").format(name=article_name) +
+ gettext("After you have requested a refund, you won't be able to read the article anymore.")
+ }}
+ </p>
+
+ <p>
+ {{
+ gettext ("You will only be able to receive the refund on the same wallet that you have used to pay for this article originally.")
+ }}
+ </p>
+
+ <form action="{{ url_for('refund', order_id=order_id) }}" method="POST">
+ <input type="text" name="article_name" value={{ article_name}} hidden>
+ <input type="submit" value="Request refund">
+ </form>
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/error.html b/talermerchantdemos/blog/templates/error.html
deleted file mode 100644
index 0d4bd02..0000000
--- a/talermerchantdemos/blog/templates/error.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
- <h1>An Error Occurred</h1>
-
- <p>{{ message }}</p>
-
- {% if status_code %}
- <p>The backend returned status code {{ status_code }}.</p>
- {% endif %}
-
- {% if json %}
- <p>Backend Response:</p>
- <pre>{{ json }}</pre>
- {% endif %}
-
- {% if stack %}
- <p>Stack trace:</p>
- <pre>
- {{ stack }}
- </pre>
- {% endif %}
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/error.html.j2 b/talermerchantdemos/blog/templates/error.html.j2
new file mode 100644
index 0000000..ffc2e1f
--- /dev/null
+++ b/talermerchantdemos/blog/templates/error.html.j2
@@ -0,0 +1,24 @@
+{% extends "templates/base.html.j2" %}
+{% block main %}
+ <h1>{{ gettext("Error encountered") }}</h1>
+
+ <p>{{ message }}</p>
+
+ {% if status_code %}
+ <p>
+ {{ gettext ("The backend returned status code {code}.").format(code=status_code) }}.
+ </p>
+ {% endif %}
+
+ {% if json %}
+ <p>{{gettext("Backend response:")}}</p>
+ <pre>{{ json }}</pre>
+ {% endif %}
+
+ {% if stack %}
+ <p>{{gettext("Stack trace:")}}</p>
+ <pre>
+ {{ stack }}
+ </pre>
+ {% endif %}
+{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/index.html b/talermerchantdemos/blog/templates/index.html
deleted file mode 100644
index e139bd2..0000000
--- a/talermerchantdemos/blog/templates/index.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends "templates/base.html" %}
-{% block main %}
- <h1>Essay Shop: Free Software, Free Society</h1>
- <div style="font-size: smaller;">
- <p>This is the second edition of <cite>Free Software, Free Society: Selected Essays of Richard M. Stallman.</cite><br>
- Free Software Foundation<br>
- 51 Franklin Street, Fifth Floor<br>
- Boston, MA 02110-1335
- <br>
- Copyright &copy; 2002, 2010 Free Software Foundation, Inc.
- </p>
-
- <p>Verbatim copying and distribution of this entire book are permitted
- worldwide, without royalty, in any medium, provided this notice is
- preserved. Permission is granted to copy and distribute translations
- of this book from the original English into another language provided
- the translation has been approved by the Free Software Foundation and
- the copyright notice and this permission notice are preserved on all
- copies.
- </p>
- <p>ISBN 978-0-9831592-0-9</p>
- </div>
-
- <h2>Chapters</h2>
- <div>
- Click on an individual chapter to to purchase it. You can
- get free, virtual money to buy articles on this page at the
- <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">bank</a>.
- </div>
-
- <div>
- {% for article in articles %}
- <div class="notice">
- <h3><a href="{{ url_for('article', lang=article.lang, article_name=article.slug) }}" class="articleTitle">{{article.title}}</a></h3>
- <p>{{ article.teaser|safe }} <a href="{{ url_for('article', lang=article.lang, article_name=article.slug) }}">(Pay to read more...)</a></p>
- </div>
- {% else %}
- <em>(No articles available)</em>
- {% endfor %}
- </div>
-{% endblock main %}
diff --git a/talermerchantdemos/blog/templates/show_refund.html b/talermerchantdemos/blog/templates/show_refund.html
deleted file mode 100644
index 913b6a5..0000000
--- a/talermerchantdemos/blog/templates/show_refund.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "templates/base.html" %}
-
-{% block main %}
-
-<h1>Refund</h1>
-
-<div class="taler-installed-hide">
- <p>
- Looks like your browser doesn't support GNU Taler payments. You can try
- installing a <a href="https://taler.net/en/wallet.html">wallet browser extension</a>.
- </p>
-</div>
-
-<div>
-
- <p>
- You can use this QR code to get a refund with your mobile wallet:
- </p>
-
- {{ qrcode_svg | safe }}
-
- <p>
- Click <a href="{{ taler_refund_uri }}">this link</a> to open your system's Taler wallet if it exists.
- </p>
-
-</div>
-
-{% endblock main %}
diff --git a/talermerchantdemos/donations/donations.py b/talermerchantdemos/donations/donations.py
index 7d4c9c6..3f021f9 100644
--- a/talermerchantdemos/donations/donations.py
+++ b/talermerchantdemos/donations/donations.py
@@ -26,7 +26,7 @@ import traceback
import urllib
from taler.util.talerconfig import TalerConfig, ConfigurationError
from urllib.parse import urljoin
-from ..httpcommon import backend_post, backend_get
+from ..httpcommon import backend_post, backend_get, fallback_404
import sys
if not sys.version_info.major == 3 and sys.version_info.minor >= 6:
@@ -242,3 +242,8 @@ def fulfillment(receiver):
currency=CURRENCY,
)
return flask.redirect(pay_status["order_status_url"])
+
+@app.errorhandler(404)
+def handler(e):
+ return flask.render_template(
+ "templates/error.html", message="Page not found")
diff --git a/talermerchantdemos/httpcommon/__init__.py b/talermerchantdemos/httpcommon/__init__.py
index fca615f..1036f33 100644
--- a/talermerchantdemos/httpcommon/__init__.py
+++ b/talermerchantdemos/httpcommon/__init__.py
@@ -44,6 +44,7 @@ def backend_post(backend_url, endpoint, json):
json=response_json,
status_code=resp.status_code
)
+ print("Backend responds to {}: {}".format(final_url, str(response_json)))
return response_json
@@ -57,7 +58,7 @@ def backend_post(backend_url, endpoint, json):
def backend_get(backend_url, endpoint, params):
headers = {"Authorization": "ApiKey sandbox"}
final_url = urljoin(backend_url, endpoint)
- print("GETting: " + final_url)
+ print("GETting: " + final_url + " with params: " + str(params))
try:
resp = requests.get(
final_url, params=params, headers=headers
@@ -75,4 +76,8 @@ def backend_get(backend_url, endpoint, params):
json=response_json,
status_code=resp.status_code
)
+ print("Backend responds to {}: {}".format(final_url, str(response_json)))
return response_json
+
+def fallback_404(error):
+ return "Page not found"
diff --git a/talermerchantdemos/survey/survey.py b/talermerchantdemos/survey/survey.py
index 562b817..b6a5ceb 100644
--- a/talermerchantdemos/survey/survey.py
+++ b/talermerchantdemos/survey/survey.py
@@ -25,7 +25,7 @@ from urllib.parse import urljoin
import flask
import traceback
from taler.util.talerconfig import TalerConfig, ConfigurationError
-from ..httpcommon import backend_get, backend_post
+from ..httpcommon import backend_get, backend_post, fallback_404
import sys
if not sys.version_info.major == 3 and sys.version_info.minor >= 6:
@@ -135,3 +135,8 @@ def index():
return flask.render_template(
"templates/index.html", merchant_currency=CURRENCY
)
+
+@app.errorhandler(404)
+def handler(e):
+ return flask.render_template(
+ "templates/error.html", message="Page not found")