From 2b84f3bd5de3a500b63193924bea2f3dfd0c9afd Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 21 Apr 2021 21:07:18 +0200 Subject: common base template, new logo and lots of cleanup --- talermerchantdemos/blog/blog.py | 190 +++++++++++++-------- talermerchantdemos/blog/content.py | 36 ++-- talermerchantdemos/blog/static | 1 - .../blog/templates/article_frame.html.j2 | 15 -- .../blog/templates/article_refunded.html.j2 | 18 -- talermerchantdemos/blog/templates/base.html.j2 | 52 ------ .../blog/templates/confirm_refund.html.j2 | 23 --- talermerchantdemos/blog/templates/error.html.j2 | 24 --- talermerchantdemos/blog/templates/footer.html.j2 | 1 - talermerchantdemos/blog/templates/index.html.j2 | 36 ---- talermerchantdemos/blog/templates/javascript.html | 31 ---- .../blog/templates/language-switcher.html.j2 | 1 - talermerchantdemos/blog/templates/menu.html.j2 | 1 - talermerchantdemos/blog/translations | 1 - talermerchantdemos/donations/donations.py | 97 +++++------ talermerchantdemos/donations/static | 1 - .../donations/templates/base.html.j2 | 48 ------ .../donations/templates/checkout.html.j2 | 50 ------ .../donations/templates/error.html.j2 | 24 --- .../donations/templates/footer.html.j2 | 1 - .../donations/templates/fulfillment.html.j2 | 22 --- .../donations/templates/index.html.j2 | 44 ----- .../donations/templates/javascript.html | 31 ---- .../donations/templates/language-switcher.html.j2 | 1 - .../donations/templates/menu.html.j2 | 1 - .../templates/provider-not-supported.html.j2 | 11 -- talermerchantdemos/donations/translations | 1 - talermerchantdemos/httpcommon/__init__.py | 65 +++---- talermerchantdemos/landing/landing.py | 16 +- talermerchantdemos/landing/static | 1 - talermerchantdemos/landing/templates/base.html.j2 | 48 ------ talermerchantdemos/landing/templates/error.html.j2 | 13 -- .../landing/templates/footer.html.j2 | 1 - talermerchantdemos/landing/templates/index.html.j2 | 89 ---------- .../landing/templates/language-switcher.html.j2 | 1 - talermerchantdemos/landing/templates/menu.html.j2 | 1 - talermerchantdemos/landing/translations | 1 - talermerchantdemos/survey/static | 1 - talermerchantdemos/survey/survey.py | 80 ++++++--- talermerchantdemos/survey/templates/base.html.j2 | 48 ------ talermerchantdemos/survey/templates/error.html.j2 | 24 --- talermerchantdemos/survey/templates/footer.html.j2 | 1 - talermerchantdemos/survey/templates/index.html.j2 | 26 --- .../survey/templates/language-switcher.html.j2 | 1 - talermerchantdemos/survey/templates/menu.html.j2 | 1 - talermerchantdemos/survey/translations | 1 - .../templates/blog-article-frame.html.j2 | 20 +++ .../templates/blog-article-refunded.html.j2 | 18 ++ talermerchantdemos/templates/blog-base.html.j2 | 13 ++ .../templates/blog-confirm-refund.html.j2 | 23 +++ talermerchantdemos/templates/blog-error.html.j2 | 24 +++ talermerchantdemos/templates/blog-index.html.j2 | 36 ++++ talermerchantdemos/templates/common-base.html.j2 | 50 ++++++ .../templates/donations-base.html.j2 | 13 ++ .../templates/donations-checkout.html.j2 | 50 ++++++ .../templates/donations-error.html.j2 | 24 +++ .../templates/donations-fulfillment.html.j2 | 22 +++ .../templates/donations-index.html.j2 | 44 +++++ .../donations-provider-not-supported.html.j2 | 11 ++ talermerchantdemos/templates/landing-base.html.j2 | 12 ++ talermerchantdemos/templates/landing-error.html.j2 | 13 ++ talermerchantdemos/templates/landing-index.html.j2 | 89 ++++++++++ talermerchantdemos/templates/menu.html.j2 | 5 +- talermerchantdemos/templates/survey-base.html.j2 | 13 ++ talermerchantdemos/templates/survey-error.html.j2 | 24 +++ talermerchantdemos/templates/survey-index.html.j2 | 26 +++ 66 files changed, 801 insertions(+), 910 deletions(-) delete mode 120000 talermerchantdemos/blog/static delete mode 100644 talermerchantdemos/blog/templates/article_frame.html.j2 delete mode 100644 talermerchantdemos/blog/templates/article_refunded.html.j2 delete mode 100644 talermerchantdemos/blog/templates/base.html.j2 delete mode 100644 talermerchantdemos/blog/templates/confirm_refund.html.j2 delete mode 100644 talermerchantdemos/blog/templates/error.html.j2 delete mode 120000 talermerchantdemos/blog/templates/footer.html.j2 delete mode 100644 talermerchantdemos/blog/templates/index.html.j2 delete mode 100644 talermerchantdemos/blog/templates/javascript.html delete mode 120000 talermerchantdemos/blog/templates/language-switcher.html.j2 delete mode 120000 talermerchantdemos/blog/templates/menu.html.j2 delete mode 120000 talermerchantdemos/blog/translations delete mode 120000 talermerchantdemos/donations/static delete mode 100644 talermerchantdemos/donations/templates/base.html.j2 delete mode 100644 talermerchantdemos/donations/templates/checkout.html.j2 delete mode 100644 talermerchantdemos/donations/templates/error.html.j2 delete mode 120000 talermerchantdemos/donations/templates/footer.html.j2 delete mode 100644 talermerchantdemos/donations/templates/fulfillment.html.j2 delete mode 100644 talermerchantdemos/donations/templates/index.html.j2 delete mode 100644 talermerchantdemos/donations/templates/javascript.html delete mode 120000 talermerchantdemos/donations/templates/language-switcher.html.j2 delete mode 120000 talermerchantdemos/donations/templates/menu.html.j2 delete mode 100644 talermerchantdemos/donations/templates/provider-not-supported.html.j2 delete mode 120000 talermerchantdemos/donations/translations delete mode 120000 talermerchantdemos/landing/static delete mode 100644 talermerchantdemos/landing/templates/base.html.j2 delete mode 100644 talermerchantdemos/landing/templates/error.html.j2 delete mode 120000 talermerchantdemos/landing/templates/footer.html.j2 delete mode 100644 talermerchantdemos/landing/templates/index.html.j2 delete mode 120000 talermerchantdemos/landing/templates/language-switcher.html.j2 delete mode 120000 talermerchantdemos/landing/templates/menu.html.j2 delete mode 120000 talermerchantdemos/landing/translations delete mode 120000 talermerchantdemos/survey/static delete mode 100644 talermerchantdemos/survey/templates/base.html.j2 delete mode 100644 talermerchantdemos/survey/templates/error.html.j2 delete mode 120000 talermerchantdemos/survey/templates/footer.html.j2 delete mode 100644 talermerchantdemos/survey/templates/index.html.j2 delete mode 120000 talermerchantdemos/survey/templates/language-switcher.html.j2 delete mode 120000 talermerchantdemos/survey/templates/menu.html.j2 delete mode 120000 talermerchantdemos/survey/translations create mode 100644 talermerchantdemos/templates/blog-article-frame.html.j2 create mode 100644 talermerchantdemos/templates/blog-article-refunded.html.j2 create mode 100644 talermerchantdemos/templates/blog-base.html.j2 create mode 100644 talermerchantdemos/templates/blog-confirm-refund.html.j2 create mode 100644 talermerchantdemos/templates/blog-error.html.j2 create mode 100644 talermerchantdemos/templates/blog-index.html.j2 create mode 100644 talermerchantdemos/templates/common-base.html.j2 create mode 100644 talermerchantdemos/templates/donations-base.html.j2 create mode 100644 talermerchantdemos/templates/donations-checkout.html.j2 create mode 100644 talermerchantdemos/templates/donations-error.html.j2 create mode 100644 talermerchantdemos/templates/donations-fulfillment.html.j2 create mode 100644 talermerchantdemos/templates/donations-index.html.j2 create mode 100644 talermerchantdemos/templates/donations-provider-not-supported.html.j2 create mode 100644 talermerchantdemos/templates/landing-base.html.j2 create mode 100644 talermerchantdemos/templates/landing-error.html.j2 create mode 100644 talermerchantdemos/templates/landing-index.html.j2 create mode 100644 talermerchantdemos/templates/survey-base.html.j2 create mode 100644 talermerchantdemos/templates/survey-error.html.j2 create mode 100644 talermerchantdemos/templates/survey-index.html.j2 (limited to 'talermerchantdemos') diff --git a/talermerchantdemos/blog/blog.py b/talermerchantdemos/blog/blog.py index be9b0a2..e8cce32 100644 --- a/talermerchantdemos/blog/blog.py +++ b/talermerchantdemos/blog/blog.py @@ -34,20 +34,38 @@ 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, self_localized, err_abort, Deadline +from talermerchantdemos.httpcommon import ( + backend_get, + backend_post, + self_localized, + Deadline, + BackendException, +) + + +def err_abort(abort_status_code, **params): + """ + Return a error response to the client. + + @param abort_status_code status code to return along the response. + @param params _kw_ arguments to passed verbatim to the templating engine. + """ + t = flask.render_template("blog-error.html.j2", lang=get_locale(), **params) + flask.abort(flask.make_response(t, abort_status_code)) 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) + assert refunded != None and refund_deadline t_ms = refund_deadline.get("t_ms") - assert(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( @@ -57,10 +75,7 @@ if not sys.version_info.major == 3 and sys.version_info.minor >= 6: ) 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="../templates", static_folder="../static") app.debug = True app.secret_key = base64.b64encode(os.urandom(64)).decode("utf-8") @@ -73,19 +88,24 @@ try: except ConfigurationError as ce: print(ce) exit(1) + ARTICLE_AMOUNT = CURRENCY + ":0.5" BACKEND_URL = urljoin(BACKEND_BASE_URL, "instances/blog/") +BABEL_TRANSLATION_DIRECTORIES = "../translations" + app.config.from_object(__name__) babel = Babel(app) -LOGGER.info("Using translations from:" + ':'.join(list(babel.translation_directories))) +LOGGER.info("Using translations from:" + ":".join(list(babel.translation_directories))) translations = [str(translation) for translation in babel.list_translations()] -if not 'en' in translations: - translations.append('en') -LOGGER.info("Operating with the following translations available: " + ' '.join(translations)) +if not "en" in translations: + translations.append("en") +LOGGER.info( + "Operating with the following translations available: " + " ".join(translations) +) -app.jinja_env.globals.update(self_localized=self_localized) +app.add_template_global(self_localized) ## @@ -98,6 +118,7 @@ def utility_processor(): # These helpers will be available in templates def env(name, default=None): return os.environ.get(name, default) + return dict(env=env) @@ -110,9 +131,10 @@ def utility_processor(): @app.errorhandler(Exception) def internal_error(e): return flask.render_template( - "templates/error.html.j2", + "blog-error.html.j2", message=gettext("Internal error"), - stack=traceback.format_exc() + stack=traceback.format_exc(), + lang=get_locale(), ) @@ -122,27 +144,29 @@ def internal_error(e): # @return response object of the index page. @app.route("/") def index(): - default = 'en' + default = "en" target = flask.request.accept_languages.best_match(translations, default) return flask.redirect("/" + target + "/", code=302) + ## # Serve the /favicon.ico requests. # # @return the favicon.ico file. @app.route("/favicon.ico") def favicon(): - LOGGER.info("will look into: " + os.path.join(app.root_path, 'static')) + LOGGER.info("will look into: " + os.path.join(app.root_path, "static")) return flask.send_from_directory( - os.path.join(app.root_path, 'static'), + os.path.join(app.root_path, "static"), "favicon.ico", - mimetype="image/vnd.microsoft.ico" + mimetype="image/vnd.microsoft.ico", ) + @babel.localeselector def get_locale(): - parts = request.path.split('/', 2) - if (2 >= len(parts)): + parts = request.path.split("/", 2) + if 2 >= len(parts): # Totally unexpected path format, do not localize return "en" lang = parts[1] @@ -150,6 +174,7 @@ def get_locale(): return lang return "en" + ## # Serve the main index page for a particular language. # @@ -157,14 +182,14 @@ def get_locale(): @app.route("//") def start(lang): if lang in ARTICLES: - translated=ARTICLES[lang] + translated = ARTICLES[lang] else: - translated={} + translated = {} return flask.render_template( - "templates/index.html.j2", + "blog-index.html.j2", lang=lang, merchant_currency=CURRENCY, - articles=translated.values() + articles=translated.values(), ) @@ -172,21 +197,30 @@ def start(lang): def confirm_refund(lang, order_id): session_id = flask.session.get("session_id", "") pay_status = backend_get( - BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id) + BACKEND_URL, + f"private/orders/{order_id}", + params=dict(session_id=session_id), + auth_token=APIKEY, ) order_status = pay_status.get("order_status") if order_status != "paid": err_abort( - 400, message=gettext("Cannot refund unpaid article"), + 400, + message=gettext("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") + "blog-error.html.j2", + message=gettext("Article is not anymore refundable"), + lang=get_locale(), ) return flask.render_template( - "templates/confirm_refund.html.j2", article_name=article_name, order_id=order_id + "blog-confirm-refund.html.j2", + article_name=article_name, + order_id=order_id, + lang=get_locale(), ) @@ -207,7 +241,10 @@ def refund(order_id): return flask.jsonify(dict(error="Aborting refund: order unknown")), 400 session_id = flask.session.get("session_id", "") pay_status = backend_get( - BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id) + BACKEND_URL, + f"private/orders/{order_id}", + params=dict(session_id=session_id), + auth_token=APIKEY, ) order_status = pay_status.get("order_status") @@ -215,16 +252,16 @@ def refund(order_id): err_abort( 402, message=gettext("You did not pay for this article (nice try!)"), - json=pay_status + json=pay_status, ) if not refundable(pay_status): err_abort( - 403, - message=gettext("Item not refundable (anymore)"), - json=pay_status + 403, message=gettext("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) + backend_post( + BACKEND_URL, f"private/orders/{order_id}/refund", refund_spec, auth_token=APIKEY + ) return flask.redirect(pay_status["order_status_url"]) @@ -245,7 +282,9 @@ def refund(order_id): def render_article(article_name, lang, data, order_id, refundable): article_info = ARTICLES[lang].get(article_name) if article_info is None: - m = gettext("Internal error: Files for article ({}) not found.").format(article_name) + m = gettext("Internal error: Files for article ({}) not found.").format( + article_name + ) err_abort(500, message=m) if data is not None: if data in article_info.extra_files: @@ -255,25 +294,27 @@ def render_article(article_name, lang, data, order_id, refundable): ) err_abort(404, message=m) # the order_id is needed for refunds + article_contents = open(get_article_file(article_info)).read() return flask.render_template( - "templates/article_frame.html.j2", - article_file=get_article_file(article_info), + "blog-article-frame.html.j2", + article_contents=article_contents, article_name=article_name, order_id=order_id, lang=lang, - refundable=refundable + refundable=refundable, ) + ## # 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): +def post_order(article_name, lang): order = dict( amount=ARTICLE_AMOUNT, - extra=dict(article_name=article_name,lang=lang), + extra=dict(article_name=article_name, lang=lang), fulfillment_url=flask.request.base_url, summary="Essay: " + article_name.replace("_", " "), # FIXME: add support for i18n of summary! @@ -283,7 +324,9 @@ def post_order(article_name,lang): order_resp = backend_post( BACKEND_URL, "private/orders", - dict(order=order, refund_delay=dict(d_ms=1000 * 120))) + dict(order=order, refund_delay=dict(d_ms=1000 * 120)), + auth_token=APIKEY, + ) return order_resp @@ -324,26 +367,28 @@ def article(article_name, lang=None, data=None): if not order_id: if not lang: err_abort(403, message=gettext("Direct access forbidden")) - order_resp = post_order(article_name,lang) + order_resp = post_order(article_name, lang) order_id = order_resp["order_id"] # Ask the backend for the status of the payment pay_status = backend_get( BACKEND_URL, f"private/orders/{order_id}", - params=dict(session_id=session_id) + params=dict(session_id=session_id), + auth_token=APIKEY, ) order_status = pay_status.get("order_status") if order_status == "claimed": if not lang: err_abort(403, message=gettext("Direct access forbidden")) # Order already claimed, must setup fresh order - order_resp = post_order(article_name,lang) + order_resp = post_order(article_name, lang) order_id = order_resp["order_id"] pay_status = backend_get( BACKEND_URL, f"private/orders/{order_id}", - params=dict(session_id=session_id) + params=dict(session_id=session_id), + auth_token=APIKEY, ) order_status = pay_status.get("order_status") # This really must be 'unpaid' now... @@ -351,17 +396,14 @@ def article(article_name, lang=None, data=None): if order_status == "paid": refunded = pay_status["refunded"] if refunded: - return flask.render_template( - "templates/article_refunded.html.j2", + return flask.render_template( + "blog-article-refunded.html.j2", article_name=article_name, order_id=order_id, + lang=lang, ) response = render_article( - article_name, - lang, - data, - order_id, - refundable(pay_status) + article_name, lang, data, order_id, refundable(pay_status) ) return response @@ -372,14 +414,10 @@ def article(article_name, lang=None, data=None): 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}") + "order_id", ai, path=urllib.parse.quote(f"/essay/{article_name}") ) response.set_cookie( - "order_id", - ai, - path=urllib.parse.quote(f"/{lang}/essay/{article_name}") + "order_id", ai, path=urllib.parse.quote(f"/{lang}/essay/{article_name}") ) return response @@ -387,27 +425,39 @@ def article(article_name, lang=None, data=None): # 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}") + "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}") + "order_id", order_id, path=urllib.parse.quote(f"/{lang}/essay/{article_name}") ) return response + @app.errorhandler(500) -def handler(e): +def handler_500(e): return flask.render_template( - "templates/error.html.j2", - message=gettext("Internal server error") + "blog-error.html.j2", + message=gettext("Internal server error"), + lang=get_locale(), ) + @app.errorhandler(404) -def handler(e): +def handler_404(e): return flask.render_template( - "templates/error.html.j2", - message=gettext("Page not found") + "blog-error.html.j2", + message=gettext("Page not found"), + lang=get_locale(), + ) + + +@app.errorhandler(BackendException) +def handler_backend_exception(e): + t = flask.render_template( + "survey-error.html.j2", + lang=get_locale(), + message=e.args[0], + json=e.backend_json, + status_code=e.backend_status, ) + return flask.make_response(t, 500) diff --git a/talermerchantdemos/blog/content.py b/talermerchantdemos/blog/content.py index 00064df..ba2fcbe 100644 --- a/talermerchantdemos/blog/content.py +++ b/talermerchantdemos/blog/content.py @@ -49,8 +49,8 @@ ARTICLES = {} # @param extra_file collection of extra files associated with the # article, like images and sounds. # @param lang language of the arcile -def add_article(slug, title, teaser, main_file, extra_files, lang='en'): - if (not (lang in ARTICLES)): +def add_article(slug, title, teaser, main_file, extra_files, lang="en"): + if not (lang in ARTICLES): ARTICLES[lang] = OrderedDict() ARTICLES[lang][slug] = Article(slug, title, teaser, main_file, extra_files, lang) @@ -71,8 +71,10 @@ def get_image_file(image): # @param article the article filename. # @return the path to the article HTML file. def get_article_file(article): - filex = resource_filename("talermerchantdemos", article.main_file) - return os.path.basename(filex) + filex = resource_filename( + "talermerchantdemos", article.main_file, + ) + return os.path.abspath(filex) ## @@ -88,7 +90,7 @@ def get_article_file(article): # specified. def add_from_html(resource_name, lang): res = resource_stream("talermerchantdemos", resource_name) - soup = BeautifulSoup(res, 'html.parser') + soup = BeautifulSoup(res, "html.parser") res.close() title_el = soup.find("h2") if title_el is None: @@ -103,9 +105,9 @@ def add_from_html(resource_name, lang): paragraphs = soup.find_all("p") if len(paragraphs) > 0: teaser = paragraphs[0].get_text() - if (len(paragraphs) > 1) and (len (teaser) < 100): + if (len(paragraphs) > 1) and (len(teaser) < 100): teaser2 = paragraphs[1].get_text() - if (len(teaser2) > len(teaser)): + if len(teaser2) > len(teaser): teaser = teaser2 else: LOGGER.warning("Cannot extract teaser from '%s'", resource_name) @@ -119,21 +121,23 @@ def add_from_html(resource_name, lang): # We require that any image whose access is regulated is src'd # as "/data/img.png". We also need to check if the # component actually matches the article's slug - if re_proc.match(img['src']): - if img['src'].split(os.sep)[2] == slug: + if re_proc.match(img["src"]): + if img["src"].split(os.sep)[2] == slug: LOGGER.info( - "extra file for %s is %s" % - (slug, os.path.basename(img['src'])) + "extra file for %s is %s" % (slug, os.path.basename(img["src"])) ) - extra_files.append(os.path.basename(img['src'])) + extra_files.append(os.path.basename(img["src"])) else: - LOGGER.warning("Image src and slug don't match: '%s' != '%s'" \ - % (img['src'].split(os.sep)[2], slug)) + LOGGER.warning( + "Image src and slug don't match: '%s' != '%s'" + % (img["src"].split(os.sep)[2], slug) + ) add_article(slug, title, teaser, resource_name, extra_files, lang) + for l in listdir(resource_filename("talermerchantdemos", "blog/articles/")): # Filter by active languages, otherwise this takes quite a while to load... - if l in { "en", "de", "sv", "es" }: + if l in {"en", "de", "sv", "es"}: LOGGER.info("importing %s" % l) - for a in listdir(resource_filename ("talermerchantdemos", "blog/articles/" + l)): + for a in listdir(resource_filename("talermerchantdemos", "blog/articles/" + l)): add_from_html("blog/articles/" + l + "/" + a, l) diff --git a/talermerchantdemos/blog/static b/talermerchantdemos/blog/static deleted file mode 120000 index d9bc54d..0000000 --- a/talermerchantdemos/blog/static +++ /dev/null @@ -1 +0,0 @@ -../static/ \ No newline at end of file diff --git a/talermerchantdemos/blog/templates/article_frame.html.j2 b/talermerchantdemos/blog/templates/article_frame.html.j2 deleted file mode 100644 index 2690306..0000000 --- a/talermerchantdemos/blog/templates/article_frame.html.j2 +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -{% include "articles/" + lang + "/" + article_file %} - -{% if refundable %} -
-

- {{ - gettext("Taler allows merchants to offer refunds to customers.") + - gettext('You can request a refund within the first hour after buying this article.').format(url=url_for('confirm_refund', lang='en', order_id=order_id)) - }} -

-{% endif %} - -{% endblock main %} diff --git a/talermerchantdemos/blog/templates/article_refunded.html.j2 b/talermerchantdemos/blog/templates/article_refunded.html.j2 deleted file mode 100644 index d4e405c..0000000 --- a/talermerchantdemos/blog/templates/article_refunded.html.j2 +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} - -

{{ gettext("Refunded") }}

- -

-{{ - gettext("Your payment (order ID {order}) for the article "{article}" has been refunded.").format(order=order_id,article=article_name) -}} -

- -

-{{ - gettext("You will not be able to read the article until you pay for it again.") -}} -

- -{% endblock main %} diff --git a/talermerchantdemos/blog/templates/base.html.j2 b/talermerchantdemos/blog/templates/base.html.j2 deleted file mode 100644 index c4935f1..0000000 --- a/talermerchantdemos/blog/templates/base.html.j2 +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - {% block meta %}{% endblock %} - {{ gettext("GNU Taler Demo: Essay Shop") }} - - - - - {% block styles %}{% endblock %} - {% block scripts %}{% endblock %} - - - -
-

{{ gettext("Taler Demo")}}
- {{ gettext("Essay shop") }}

-

{{ - gettext("On this page you can buy articles using an imaginary currency.") + "
" + - gettext("The articles are chapters from Richard Stallman's book "Free Software, Free Society".") + "
" + - gettext('The book is published by the FSF and available gratis at gnu.org.').format(shop="https://shop.fsf.org/product/free-software-free-society-2", gnu="https://www.gnu.org") - }} -

-
- {% from 'templates/menu.html.j2' import menu with context %} {{ menu('blog') }} - -
- {% block main %} - This is the main content of the page. - {% endblock %} - {% include 'templates/footer.html.j2' %} -
- - diff --git a/talermerchantdemos/blog/templates/confirm_refund.html.j2 b/talermerchantdemos/blog/templates/confirm_refund.html.j2 deleted file mode 100644 index a356ea1..0000000 --- a/talermerchantdemos/blog/templates/confirm_refund.html.j2 +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -

{{ gettext("Confirm refund request for article") }}

- -

- {{ - gettext("Do you want to get a refund for the article {name}?").format(name=article_name) + "
" + - gettext("In this demonstration, refunds will be automatically approved by the merchant.") + "
" + - gettext("After you have obtained a refund, you will not be able to read the article anymore.") - }} -

- -

- {{ - gettext ("You will only be able to receive the refund on the same wallet that you have used to pay for this article originally.") - }} -

- -
- - -
-{% endblock main %} diff --git a/talermerchantdemos/blog/templates/error.html.j2 b/talermerchantdemos/blog/templates/error.html.j2 deleted file mode 100644 index ffc2e1f..0000000 --- a/talermerchantdemos/blog/templates/error.html.j2 +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -

{{ gettext("Error encountered") }}

- -

{{ message }}

- - {% if status_code %} -

- {{ gettext ("The backend returned status code {code}.").format(code=status_code) }}. -

- {% endif %} - - {% if json %} -

{{gettext("Backend response:")}}

-
{{ json }}
- {% endif %} - - {% if stack %} -

{{gettext("Stack trace:")}}

-
-    {{ stack }}
-  
- {% endif %} -{% endblock main %} diff --git a/talermerchantdemos/blog/templates/footer.html.j2 b/talermerchantdemos/blog/templates/footer.html.j2 deleted file mode 120000 index 028b093..0000000 --- a/talermerchantdemos/blog/templates/footer.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/footer.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/blog/templates/index.html.j2 b/talermerchantdemos/blog/templates/index.html.j2 deleted file mode 100644 index f231e6f..0000000 --- a/talermerchantdemos/blog/templates/index.html.j2 +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -

{{ gettext("Essay Shop: Free Software, Free Society") }}

-
-

{{ gettext("This is the latest edition of Free Software, Free Society: Selected Essays of Richard M. Stallman.") }}
- Free Software Foundation
- 51 Franklin Street, Fifth Floor
- Boston, MA 02110-1335 -
- Copyright © 2002, 2010 Free Software Foundation, Inc. -

- -

{{ gettext("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.") }} -

-

ISBN 978-0-9831592-0-9

-
- -

{{ gettext("Chapters") }}

-
- {{ - gettext("Click on an individual chapter to to purchase it with GNU Taler.") + - gettext('You can get free, virtual money to buy articles on this page at the bank').format(env('TALER_ENV_URL_BANK')) - }} -
- -
- {% for article in articles %} - - {% else %} - ({{ gettext("No articles available in this language.") }}) - {% endfor %} -
-{% endblock main %} diff --git a/talermerchantdemos/blog/templates/javascript.html b/talermerchantdemos/blog/templates/javascript.html deleted file mode 100644 index 436c186..0000000 --- a/talermerchantdemos/blog/templates/javascript.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - JavaScript disclaimer. - - -

This site does not use JavaScript.

- - - - - - diff --git a/talermerchantdemos/blog/templates/language-switcher.html.j2 b/talermerchantdemos/blog/templates/language-switcher.html.j2 deleted file mode 120000 index 6423c5c..0000000 --- a/talermerchantdemos/blog/templates/language-switcher.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/language-switcher.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/blog/templates/menu.html.j2 b/talermerchantdemos/blog/templates/menu.html.j2 deleted file mode 120000 index b9575ea..0000000 --- a/talermerchantdemos/blog/templates/menu.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/menu.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/blog/translations b/talermerchantdemos/blog/translations deleted file mode 120000 index 0a951f7..0000000 --- a/talermerchantdemos/blog/translations +++ /dev/null @@ -1 +0,0 @@ -../../translations/ \ No newline at end of file diff --git a/talermerchantdemos/donations/donations.py b/talermerchantdemos/donations/donations.py index ea9612c..19f1372 100644 --- a/talermerchantdemos/donations/donations.py +++ b/talermerchantdemos/donations/donations.py @@ -31,19 +31,23 @@ import traceback import urllib from taler.util.talerconfig import TalerConfig, ConfigurationError from urllib.parse import urljoin -from ..httpcommon import backend_post, backend_get, fallback_404, self_localized +from ..httpcommon import backend_post, backend_get, self_localized import sys 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) LOGGER = logging.getLogger(__name__) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -app = flask.Flask(__name__, template_folder=BASE_DIR) +app = flask.Flask(__name__, template_folder="../templates", static_folder="../static") app.debug = True app.secret_key = base64.b64encode(os.urandom(64)).decode("utf-8") @@ -56,23 +60,27 @@ except ConfigurationError as ce: print(ce) exit(1) +BABEL_TRANSLATION_DIRECTORIES = "../translations" + app.config.from_object(__name__) babel = Babel(app) -LOGGER.info("Using translations from:" + ':'.join(list(babel.translation_directories))) +LOGGER.info("Using translations from:" + ":".join(list(babel.translation_directories))) translations = [str(translation) for translation in babel.list_translations()] -if not 'en' in translations: - translations.append('en') -LOGGER.info("Operating with the following translations available: " + ' '.join(translations)) +if not "en" in translations: + translations.append("en") +LOGGER.info( + "Operating with the following translations available: " + " ".join(translations) +) -app.jinja_env.globals.update(self_localized=self_localized) +app.add_template_global(self_localized) @babel.localeselector def get_locale(): - parts = request.path.split('/', 2) - if (2 >= len(parts)): + parts = request.path.split("/", 2) + if 2 >= len(parts): # Totally unexpected path format, do not localize return "en" lang = parts[1] @@ -100,10 +108,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.j2", - lang=get_locale(), - **params) + t = flask.render_template("donations-error.html.j2", lang=get_locale(), **params) flask.abort(flask.make_response(t, abort_status_code)) @@ -116,7 +121,7 @@ def err_abort(abort_status_code, **params): # if something unexpected happens. def backend_instanced_get(instance, endpoint, params): backend_url = urljoin(BACKEND_BASE_URL, f"instances/{instance}/") - return backend_get(backend_url, endpoint, params) + return backend_get(backend_url, endpoint, params, auth_token=APIKEY) ## @@ -129,7 +134,7 @@ def backend_instanced_get(instance, endpoint, params): # @return the backend response (JSON format). def backend_instanced_post(instance, endpoint, json): backend_url = urljoin(BACKEND_BASE_URL, f"instances/{instance}/") - return backend_post(backend_url, endpoint, json) + return backend_post(backend_url, endpoint, json, auth_token=APIKEY) ## @@ -153,32 +158,34 @@ def expect_parameter(name): @app.errorhandler(Exception) def internal_error(e): return flask.render_template( - "templates/error.html.j2", + "donations-error.html.j2", message=gettext("Internal error"), lang=get_locale(), - stack=traceback.format_exc() + stack=traceback.format_exc(), ) + ## # Serve the /favicon.ico requests. # # @return the favicon.ico file. @app.route("/favicon.ico") def favicon(): - LOGGER.info("will look into: " + os.path.join(app.root_path, 'static')) + LOGGER.info("will look into: " + os.path.join(app.root_path, "static")) return flask.send_from_directory( - os.path.join(app.root_path, 'static'), + os.path.join(app.root_path, "static"), "favicon.ico", - mimetype="image/vnd.microsoft.ico" + mimetype="image/vnd.microsoft.ico", ) + ## # Serve the main index page, redirecting to // # # @return response object of the index page. @app.route("/") def index(): - default = 'en' + default = "en" target = flask.request.accept_languages.best_match(translations, default) return flask.redirect("/" + target + "/", code=302) @@ -190,21 +197,10 @@ def index(): @app.route("//") def start(lang): return flask.render_template( - "templates/index.html.j2", - lang=lang, - merchant_currency=CURRENCY + "donations-index.html.j2", lang=lang, merchant_currency=CURRENCY ) -## -# Serve the "/javascript" page. -# -# @return response object for the /javascript page. -@app.route("/javascript") -def javascript_licensing(): - return flask.render_template("templates/javascript.html") - - ## # Serve the "/checkout" page. This page lets the # user pick the payment method they want to use, @@ -217,7 +213,7 @@ def checkout(lang): donation_receiver = expect_parameter("donation_receiver") donation_donor = expect_parameter("donation_donor") return flask.render_template( - "templates/checkout.html.j2", + "donations-checkout.html.j2", donation_amount=amount, donation_receiver=donation_receiver, donation_donor=donation_donor, @@ -232,11 +228,8 @@ def checkout(lang): # # @return response object about the mentioned impossibility. @app.route("//provider-not-supported") -def provider_not_supported(): - return flask.render_template( - "templates/provider-not-supported.html.j2", - lang=lang - ) +def provider_not_supported(lang): + return flask.render_template("donations-provider-not-supported.html.j2", lang=lang) ## @@ -253,8 +246,7 @@ def donate(lang): donation_donor = expect_parameter("donation_donor") payment_system = expect_parameter("payment_system") if payment_system != "taler": - return flask.redirect(flask.url_for("provider_not_supported", - lang=lang)) + return flask.redirect(flask.url_for("provider_not_supported", lang=lang)) fulfillment_url = flask.url_for( "fulfillment", timestamp=str(time.time()), @@ -266,9 +258,7 @@ def donate(lang): order = dict( amount=donation_amount, extra=dict( - donor=donation_donor, - receiver=donation_receiver, - amount=donation_amount + donor=donation_donor, receiver=donation_receiver, amount=donation_amount ), fulfillment_url=fulfillment_url, summary="Donation to {}".format(donation_receiver), @@ -279,10 +269,9 @@ def donate(lang): ) order_id = order_resp["order_id"] return flask.redirect( - flask.url_for("fulfillment", - receiver=donation_receiver, - lang=lang, - order_id=order_id) + flask.url_for( + "fulfillment", receiver=donation_receiver, lang=lang, order_id=order_id + ) ) @@ -295,9 +284,8 @@ def donate(lang): # page is returned; otherwise, the browser will be redirected # to a page that accepts the payment. @app.route("//donation/") -def fulfillment(lang,receiver): +def fulfillment(lang, receiver): order_id = expect_parameter("order_id") - pay_params = dict(order_id=order_id) pay_status = backend_instanced_get( receiver, f"private/orders/{order_id}", params=dict() ) @@ -305,7 +293,7 @@ def fulfillment(lang,receiver): if order_status == "paid": extra = pay_status["contract_terms"]["extra"] return flask.render_template( - "templates/fulfillment.html.j2", + "donations-fulfillment.html.j2", donation_receiver=extra["receiver"], donation_amount=extra["amount"], donation_donor=extra["donor"], @@ -315,10 +303,9 @@ def fulfillment(lang,receiver): ) return flask.redirect(pay_status["order_status_url"]) + @app.errorhandler(404) def handler(e): return flask.render_template( - "templates/error.html.j2", - lang=get_locale(), - message=gettext("Page not found") + "donations-error.html.j2", lang=get_locale(), message=gettext("Page not found") ) diff --git a/talermerchantdemos/donations/static b/talermerchantdemos/donations/static deleted file mode 120000 index d9bc54d..0000000 --- a/talermerchantdemos/donations/static +++ /dev/null @@ -1 +0,0 @@ -../static/ \ No newline at end of file diff --git a/talermerchantdemos/donations/templates/base.html.j2 b/talermerchantdemos/donations/templates/base.html.j2 deleted file mode 100644 index 307705f..0000000 --- a/talermerchantdemos/donations/templates/base.html.j2 +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - {{ gettext("GNU Taler Demo: Donations") }} - - - - - {% block styles %}{% endblock %} - {% block scripts %}{% endblock %} - - - -
-

{{ gettext("Taler Demo") }}
- {{gettext("Donations")}}

-

{{ - gettext ("This is the donation page.") + "
" + - gettext ("Using this page you can make donations in {currency} to Free Software projects.").format(currency=merchant_currency) - }} -

-
- {% from 'templates/menu.html.j2' import menu with context %} {{ menu('donations') }} - -
- {% block main %} - This is the main content of the page. - {% endblock %} - {% include 'templates/footer.html.j2' %} -
- - diff --git a/talermerchantdemos/donations/templates/checkout.html.j2 b/talermerchantdemos/donations/templates/checkout.html.j2 deleted file mode 100644 index 5b6900f..0000000 --- a/talermerchantdemos/donations/templates/checkout.html.j2 +++ /dev/null @@ -1,50 +0,0 @@ -{% extends "templates/base.html.j2" %} - -{% block main %} -
-

{{ gettext("Select your payment method") }}

- -

- {{ - gettext('This is an example for a "checkout" page of a Web shop.') + "" + - gettext('On the previous page, you have created the shopping cart and decided which product to buy (i.e. which project to donate KUDOS to).') + "
" + - gettext('As Taler is not yet universally used, we expect merchants will offer various payment options.') + "
" + - gettext('To continue with the demo, select the "Taler" payment option.') - }} -

-

- {{ - gettext('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.') + "" + - gettext('It would be 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.') - }} -

- -
-

- - - - - -
-
- -
- -{% endblock main %} diff --git a/talermerchantdemos/donations/templates/error.html.j2 b/talermerchantdemos/donations/templates/error.html.j2 deleted file mode 100644 index ffc2e1f..0000000 --- a/talermerchantdemos/donations/templates/error.html.j2 +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -

{{ gettext("Error encountered") }}

- -

{{ message }}

- - {% if status_code %} -

- {{ gettext ("The backend returned status code {code}.").format(code=status_code) }}. -

- {% endif %} - - {% if json %} -

{{gettext("Backend response:")}}

-
{{ json }}
- {% endif %} - - {% if stack %} -

{{gettext("Stack trace:")}}

-
-    {{ stack }}
-  
- {% endif %} -{% endblock main %} diff --git a/talermerchantdemos/donations/templates/footer.html.j2 b/talermerchantdemos/donations/templates/footer.html.j2 deleted file mode 120000 index 028b093..0000000 --- a/talermerchantdemos/donations/templates/footer.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/footer.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/donations/templates/fulfillment.html.j2 b/talermerchantdemos/donations/templates/fulfillment.html.j2 deleted file mode 100644 index 5fd799b..0000000 --- a/talermerchantdemos/donations/templates/fulfillment.html.j2 +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "templates/base.html.j2" %} - -{% block main %} -

{{ gettext("Donation Receipt") }}

-

- {{ - gettext("Thank you, {donor}, for donating {amount} to {receiver}.").format(donor=donation_donor,amount=donation_amount,receiver=donation_receiver) - }} -

-

- {{ - gettext("Please keep the order identifier {id} as a receipt for your donation.").format(id=order_id) + - gettext('You can show other people that you donated by sharing this link with them.').format(link=request.url) - }} -

- -

- {{ - gettext('You can always make another donation.').format(link=url_for('index')) - }} -

-{% endblock main %} diff --git a/talermerchantdemos/donations/templates/index.html.j2 b/talermerchantdemos/donations/templates/index.html.j2 deleted file mode 100644 index 87bce85..0000000 --- a/talermerchantdemos/donations/templates/index.html.j2 +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "templates/base.html.j2" %} - -{% block main %} -

{{ gettext("Donate to Free Software projects") }}

- -

-{{ - gettext("This donations website shows the user experience for donations with GNU Taler.") + "
" + - gettext("You can make donations in an toy currency ({currency})").format(currency=merchant_currency) -}} -

- -
-

- {{ - gettext("Please select a project, the amount (*) of {currency} you wish to donate, and enter the donor's name that will appear on your receipt:").format(currency=merchant_currency) - }} -

- -
-
- - - - -
-
-

- {{ - gettext("(*) To make the demo a bit more interesting, the 5 {currency} option is deliberately implemented with a fault: the merchant will try to make you donate 6 {currency} instead of the 5 {currency} shown in the form. But do not worry, you will be given the opportunity to review the final offer from the merchant in the Taler wallet. That way, Taler protects you from committing to erroneous payments.").format(currency=merchant_currency) - }} -

-
- -{% endblock %} diff --git a/talermerchantdemos/donations/templates/javascript.html b/talermerchantdemos/donations/templates/javascript.html deleted file mode 100644 index 436c186..0000000 --- a/talermerchantdemos/donations/templates/javascript.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - JavaScript disclaimer. - - -

This site does not use JavaScript.

- - - - - - diff --git a/talermerchantdemos/donations/templates/language-switcher.html.j2 b/talermerchantdemos/donations/templates/language-switcher.html.j2 deleted file mode 120000 index 6423c5c..0000000 --- a/talermerchantdemos/donations/templates/language-switcher.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/language-switcher.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/donations/templates/menu.html.j2 b/talermerchantdemos/donations/templates/menu.html.j2 deleted file mode 120000 index b9575ea..0000000 --- a/talermerchantdemos/donations/templates/menu.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/menu.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/donations/templates/provider-not-supported.html.j2 b/talermerchantdemos/donations/templates/provider-not-supported.html.j2 deleted file mode 100644 index a4d5d80..0000000 --- a/talermerchantdemos/donations/templates/provider-not-supported.html.j2 +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "templates/base.html" %} - -{% block main %} -

{{ gettext("Payment Provider Not Supported") }}

-

-{{ - gettext("Unfortunately the selected payment provider is not supported in this demo.") + "
" + - gettext("Please go back and select "Taler".") -}} -

-{% endblock main %} diff --git a/talermerchantdemos/donations/translations b/talermerchantdemos/donations/translations deleted file mode 120000 index 0a951f7..0000000 --- a/talermerchantdemos/donations/translations +++ /dev/null @@ -1 +0,0 @@ -../../translations/ \ No newline at end of file diff --git a/talermerchantdemos/httpcommon/__init__.py b/talermerchantdemos/httpcommon/__init__.py index 443160b..39cd696 100644 --- a/talermerchantdemos/httpcommon/__init__.py +++ b/talermerchantdemos/httpcommon/__init__.py @@ -6,6 +6,16 @@ from datetime import datetime import time from flask_babel import gettext + +class BackendException(Exception): + """Exception for failed communication with the Taler merchant backend""" + + def __init__(self, message, backend_status=None, backend_json=None): + super().__init__(message) + self.backend_status = backend_status + self.backend_json = backend_json + + ## # POST a request to the backend, and return a error # response if any error occurs. @@ -14,28 +24,30 @@ from flask_babel import gettext # this request. # @param json the POST's body. # @return the backend response (JSON format). -def backend_post(backend_url, endpoint, json): - headers = {"Authorization": "ApiKey sandbox"} +def backend_post(backend_url, endpoint, json, auth_token=None): + headers = dict() + if auth_token is not None: + headers["Authorization"] = "Bearer " + auth_token final_url = urljoin(backend_url, endpoint) print("POSTing to: " + final_url) try: resp = requests.post(final_url, json=json, headers=headers) except requests.ConnectionError: - err_abort(500, message=gettext("Could not establish connection to backend")) + raise BackendException( + message=gettext("Could not establish connection to backend") + ) try: response_json = resp.json() except ValueError: - err_abort( - 500, + raise BackendException( message=gettext("Could not parse response from backend"), - status_code=resp.status_code, + backend_status=resp.status_code, ) if resp.status_code != 200: - err_abort( - 500, + raise BackendException( message=gettext("Backend returned error status"), - json=response_json, - status_code=resp.status_code, + backend_status=resp.status_code, + backend_json=response_json, ) print("Backend responds to {}: {}".format(final_url, str(response_json))) return response_json @@ -48,24 +60,27 @@ def backend_post(backend_url, endpoint, json): # @param params (dict type of) URL parameters to append to the request. # @return the JSON response from the backend, or a error response # if something unexpected happens. -def backend_get(backend_url, endpoint, params): - headers = {"Authorization": "ApiKey sandbox"} +def backend_get(backend_url, endpoint, params, auth_token=None): + headers = dict() + if auth_token is not None: + headers["Authorization"] = "Bearer " + auth_token final_url = urljoin(backend_url, endpoint) print("GETting: " + final_url + " with params: " + str(params)) try: resp = requests.get(final_url, params=params, headers=headers) except requests.ConnectionError: - err_abort(500, message=gettext("Could not establish connection to backend")) + raise BackendException( + message=gettext("Could not establish connection to backend") + ) try: response_json = resp.json() except ValueError: - err_abort(500, message=gettext("Could not parse response from backend")) + raise BackendException(message=gettext("Could not parse response from backend")) if resp.status_code != 200: - err_abort( - 500, + raise BackendException( message=gettext("Backend returned error status"), - json=response_json, - status_code=resp.status_code, + backend_status=resp.status_code, + backend_json=response_json, ) print("Backend responds to {}: {}".format(final_url, str(response_json))) return response_json @@ -98,20 +113,6 @@ def self_localized(lang): return "/" + lang + "/" + parts[2] -## -# Return a error response to the client. -# -# @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.j2", lang=get_locale(), **params) - flask.abort(flask.make_response(t, abort_status_code)) - - -def fallback_404(error): - return "Page not found" - - class Deadline: def __init__(self, value): self.value = value diff --git a/talermerchantdemos/landing/landing.py b/talermerchantdemos/landing/landing.py index ffbedd7..270e205 100644 --- a/talermerchantdemos/landing/landing.py +++ b/talermerchantdemos/landing/landing.py @@ -41,8 +41,7 @@ if not sys.version_info.major == 3 and sys.version_info.minor >= 6: ) sys.exit(1) -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -app = flask.Flask(__name__, template_folder=BASE_DIR) +app = flask.Flask(__name__, template_folder="../templates", static_folder="../static") app.debug = True app.secret_key = base64.b64encode(os.urandom(64)).decode("utf-8") @@ -56,6 +55,8 @@ except ConfigurationError as ce: print(ce) exit(1) +BABEL_TRANSLATION_DIRECTORIES = "../translations" + app.config.from_object(__name__) babel = Babel(app) @@ -67,8 +68,7 @@ LOGGER.info( "Operating with the following translations available: " + " ".join(translations) ) -# pylint: disable=no-member -app.jinja_env.globals.update(self_localized=self_localized) +app.add_template_global(self_localized) @babel.localeselector @@ -113,7 +113,7 @@ def utility_processor(): @app.errorhandler(Exception) def internal_error(e): return flask.render_template( - "templates/error.html.j2", + "landing-error.html.j2", message=gettext("Internal error"), stack=traceback.format_exc(), lang=get_locale(), @@ -173,7 +173,7 @@ def start(lang): merchant_survey_url = "#" return flask.render_template( - "templates/index.html.j2", + "landing-index.html.j2", merchant_currency=CURRENCY, lang=lang, bank_url=bank_register_url, @@ -186,14 +186,14 @@ def start(lang): @app.errorhandler(404) def handler_404(e): return flask.render_template( - "templates/error.html.j2", message=gettext("Page not found"), lang=get_locale() + "landing-error.html.j2", message=gettext("Page not found"), lang=get_locale() ) @app.errorhandler(405) def handler_405(e): return flask.render_template( - "templates/error.html.j2", + "landing-error.html.j2", message=gettext("HTTP method not allowed for this page"), lang=get_locale(), ) diff --git a/talermerchantdemos/landing/static b/talermerchantdemos/landing/static deleted file mode 120000 index d9bc54d..0000000 --- a/talermerchantdemos/landing/static +++ /dev/null @@ -1 +0,0 @@ -../static/ \ No newline at end of file diff --git a/talermerchantdemos/landing/templates/base.html.j2 b/talermerchantdemos/landing/templates/base.html.j2 deleted file mode 100644 index 97a91a5..0000000 --- a/talermerchantdemos/landing/templates/base.html.j2 +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - {{ gettext("GNU Taler Demo: Introduction") }} - - - - - {% block styles %}{% endblock %} - {% block scripts %}{% endblock %} - - - -
-


- {{ gettext("Introduction") }}

-

{{ - gettext("This is the GNU Taler demo.") + "
" + - gettext("Here you can try out the GNU Taler payment system using a toy currency.") - }} -

-
- {% from 'templates/menu.html.j2' import menu with context %} {{ menu('landing') }} - -
- {% block main %} - This is the main content of the page. - {% endblock %} - {% include 'templates/footer.html.j2' %} -
- - diff --git a/talermerchantdemos/landing/templates/error.html.j2 b/talermerchantdemos/landing/templates/error.html.j2 deleted file mode 100644 index 009947c..0000000 --- a/talermerchantdemos/landing/templates/error.html.j2 +++ /dev/null @@ -1,13 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -

{{ gettext("Error encountered") }}

- -

{{ message }}

- - {% if stack %} -

{{gettext("Stack trace:")}}

-
-    {{ stack }}
-  
- {% endif %} -{% endblock main %} diff --git a/talermerchantdemos/landing/templates/footer.html.j2 b/talermerchantdemos/landing/templates/footer.html.j2 deleted file mode 120000 index 028b093..0000000 --- a/talermerchantdemos/landing/templates/footer.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/footer.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/landing/templates/index.html.j2 b/talermerchantdemos/landing/templates/index.html.j2 deleted file mode 100644 index 9beb7c0..0000000 --- a/talermerchantdemos/landing/templates/index.html.j2 +++ /dev/null @@ -1,89 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -
-

{{ gettext("Step 1: Install the Taler wallet") }}

-

- {{ - gettext('Install the wallet from the installation page.').format(link="https://wallet.taler.net/") + "
" + - gettext('Installation only takes one click.') - }} -

-

- {{ gettext('After installation, you may be asked to grant the browser-based Taler wallet additional optional permissions that allow it to improve your user experience.') }}
- {{ gettext('These permissions will allow the wallet to automatically open if a page asks for a Taler payment.') }}
- {{ gettext('Regardless of the permissions you grant, the wallet will never transmit information about you or your browsing history to anyone.') }} -

-
- -
-

{{ gettext("Step 2: Withdraw coins") }}

-

- {{ - gettext('In this demo you are paying with {currency}, an imaginary currency.').format(currency=merchant_currency) + "
" + - gettext('To withdraw {currency} coins you must first create an account at our bank.').format(currency=merchant_currency,bank=bank_url) + "
" + - gettext('Signing up only requires you to pick a username and password.') + "
" + - gettext('When you create an account at our bank, you will be credited 100 {currency} for signing up.').format(currency=merchant_currency) + "
" + - gettext("Afterwards, use the bank's Web interface to authorize the transfer of {currency} to your wallet.").format(currency=merchant_currency) - }} -

-

- {{ gettext('Once you have completed this step, you can click on the Taler icon in your browser to check your balance.') }} -

-
- -
-

{{ gettext("Step 3: Pay") }}

-

- {{ - gettext("We have two demo merchants where you can spend your coins:") - }} -

-
    -
  • - {{ - gettext('At the essay store you can pay in {currency} for individual chapters of Richard Stallman's book "Free Software, Free Society".').format(blog=merchant_blog_url,currency=merchant_currency) + "
    " + - gettext('The book is also available for free at the FSF.').format(fsf="http://www.fsf.org/") - }} -
  • -
  • - {{ - gettext('At the project donation website you can show respect to a software project of your choice by donating {currency} to them.').format(donations=merchant_donations_url,currency=merchant_currency) - }} -
  • -
-
-
-

{{ gettext("Step 4: Check money flow") }}

-

- {{ - gettext('You can see the wire transfers from the escrow account of the exchange to the merchants on the public accounts page of the bank.').format(bank=bank_url) + "
" + - gettext('Note that only accounts configured to be publicly viewable are shown on that page.') - }} -

-
-
-

{{ gettext("Step 5: Survey") }}

-

- {{ - gettext("Websites can give tips to visitors for completing tasks.") + "
" + - gettext('You can earn some {currency} coins by filling in our survey.').format(url=merchant_survey_url,currency=merchant_currency) - }} -

-
- -
-

{{ gettext("Step 6: Reach out to us") }}

-

- {{ - gettext("We appreciate feedback about Taler and this demonstrator.") + "
" + - gettext('Let us know what you think by contacting us.').format(link="mailto:demo-feedback@taler.net") - }} -

-
-{% endblock %} diff --git a/talermerchantdemos/landing/templates/language-switcher.html.j2 b/talermerchantdemos/landing/templates/language-switcher.html.j2 deleted file mode 120000 index 6423c5c..0000000 --- a/talermerchantdemos/landing/templates/language-switcher.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/language-switcher.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/landing/templates/menu.html.j2 b/talermerchantdemos/landing/templates/menu.html.j2 deleted file mode 120000 index b9575ea..0000000 --- a/talermerchantdemos/landing/templates/menu.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/menu.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/landing/translations b/talermerchantdemos/landing/translations deleted file mode 120000 index 0a951f7..0000000 --- a/talermerchantdemos/landing/translations +++ /dev/null @@ -1 +0,0 @@ -../../translations/ \ No newline at end of file diff --git a/talermerchantdemos/survey/static b/talermerchantdemos/survey/static deleted file mode 120000 index d9bc54d..0000000 --- a/talermerchantdemos/survey/static +++ /dev/null @@ -1 +0,0 @@ -../static/ \ No newline at end of file diff --git a/talermerchantdemos/survey/survey.py b/talermerchantdemos/survey/survey.py index 871b417..4813477 100644 --- a/talermerchantdemos/survey/survey.py +++ b/talermerchantdemos/survey/survey.py @@ -30,18 +30,21 @@ from flask_babel import force_locale from flask_babel import gettext import traceback from taler.util.talerconfig import TalerConfig, ConfigurationError -from ..httpcommon import backend_get, backend_post, self_localized +from ..httpcommon import backend_get, backend_post, self_localized, BackendException import sys 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) +app = flask.Flask(__name__, template_folder="../templates", static_folder="../static") app.debug = True -app.secret_key = base64.b64encode(os.urandom(64)).decode('utf-8') +app.secret_key = base64.b64encode(os.urandom(64)).decode("utf-8") LOGGER = logging.getLogger(__name__) TC = TalerConfig.from_env() @@ -53,22 +56,27 @@ except ConfigurationError as ce: print(ce) exit(1) +BABEL_TRANSLATION_DIRECTORIES = "../translations" + app.config.from_object(__name__) babel = Babel(app) INSTANCED_URL = urljoin(BACKEND_URL, f"instances/survey/") -LOGGER.info("Using translations from:" + ':'.join(list(babel.translation_directories))) +LOGGER.info("Using translations from:" + ":".join(list(babel.translation_directories))) translations = [str(translation) for translation in babel.list_translations()] -if not 'en' in translations: - translations.append('en') -LOGGER.info("Operating with the following translations available: " + ' '.join(translations)) +if not "en" in translations: + translations.append("en") +LOGGER.info( + "Operating with the following translations available: " + " ".join(translations) +) + +app.add_template_global(self_localized) -app.jinja_env.globals.update(self_localized=self_localized) @babel.localeselector def get_locale(): - parts = request.path.split('/', 2) - if (2 >= len(parts)): + parts = request.path.split("/", 2) + if 2 >= len(parts): # Totally unexpected path format, do not localize return "en" lang = parts[1] @@ -76,6 +84,7 @@ def get_locale(): return lang return "en" + ## # Make the environment available into templates. # @@ -106,23 +115,24 @@ def utility_processor(): @app.errorhandler(Exception) def internal_error(e): return flask.render_template( - "templates/error.html.j2", + "survey-error.html.j2", message=gettext("Internal error"), stack=traceback.format_exc(), - lang=get_locale() + lang=get_locale(), ) + ## # Serve the /favicon.ico requests. # # @return the favicon.ico file. @app.route("/favicon.ico") def favicon(): - LOGGER.info("will look into: " + os.path.join(app.root_path, 'static')) + LOGGER.info("will look into: " + os.path.join(app.root_path, "static")) return flask.send_from_directory( - os.path.join(app.root_path, 'static'), + os.path.join(app.root_path, "static"), "favicon.ico", - mimetype="image/vnd.microsoft.ico" + mimetype="image/vnd.microsoft.ico", ) @@ -139,21 +149,23 @@ def submit_survey(lang): tip_spec = dict( amount=CURRENCY + ":1.0", next_url=os.environ.get("TALER_ENV_URL_INTRO", "https://taler.net/"), - justification="Payment methods survey" + justification="Payment methods survey", ) - backend_resp = backend_post(INSTANCED_URL, "private/tips", tip_spec) + backend_resp = backend_post(INSTANCED_URL, "private/tips", tip_spec, auth_token=APIKEY) return flask.redirect(backend_resp["tip_status_url"]) + ## # Serve the main index page, redirecting to // # # @return response object of the index page. @app.route("/") def index(): - default = 'en' + default = "en" target = flask.request.accept_languages.best_match(translations, default) return flask.redirect("/" + target + "/", code=302) + ## # Serve the internationalized main index page. # @@ -161,21 +173,33 @@ def index(): @app.route("//", methods=["GET"]) def start(lang): return flask.render_template( - "templates/index.html.j2", merchant_currency=CURRENCY, lang=lang + "survey-index.html.j2", merchant_currency=CURRENCY, lang=lang ) + @app.errorhandler(404) -def handler(e): +def handler_404(e): return flask.render_template( - "templates/error.html.j2", - message=gettext("Page not found"), - lang=get_locale() + "survey-error.html.j2", message=gettext("Page not found"), lang=get_locale() ) + @app.errorhandler(405) -def handler(e): +def handler_405(e): return flask.render_template( - "templates/error.html.j2", + "survey-error.html.j2", message=gettext("HTTP method not allowed for this page"), - lang=get_locale() + lang=get_locale(), + ) + + +@app.errorhandler(BackendException) +def handler_backend_exception(e): + t = flask.render_template( + "survey-error.html.j2", + lang=get_locale(), + message=e.args[0], + json=e.backend_json, + status_code=e.backend_status, ) + return flask.make_response(t, 500) \ No newline at end of file diff --git a/talermerchantdemos/survey/templates/base.html.j2 b/talermerchantdemos/survey/templates/base.html.j2 deleted file mode 100644 index 734da6f..0000000 --- a/talermerchantdemos/survey/templates/base.html.j2 +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - {{ gettext("GNU Taler Demo: Survey") }} - - - - - {% block styles %}{% endblock %} - {% block scripts %}{% endblock %} - - - -
-

{{ gettext("Taler Demo") }}
- {{ gettext("Survey") }}

-

{{ - gettext("This page demonstrates how to tip visitors for completing small tasks.") + "
" + - gettext("Tipping is a way for offer cash rewards that go directly into a user's wallet.") - }} -

-
- {% from 'templates/menu.html.j2' import menu with context %} {{ menu('survey') }} - -
- {% block main %} - This is the main content of the page. - {% endblock %} - {% include 'templates/footer.html.j2' %} -
- - diff --git a/talermerchantdemos/survey/templates/error.html.j2 b/talermerchantdemos/survey/templates/error.html.j2 deleted file mode 100644 index 844da08..0000000 --- a/talermerchantdemos/survey/templates/error.html.j2 +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -

{{ gettext("Error encountered") }}

- -

{{ message }}

- - {% if status_code %} -

- {{ gettext ("The backend returned status code {code}.").format(code=status_code) }} -

- {% endif %} - - {% if json %} -

{{gettext("Backend response:")}}

-
{{ json }}
- {% endif %} - - {% if stack %} -

{{gettext("Stack trace:")}}

-
-    {{ stack }}
-  
- {% endif %} -{% endblock main %} diff --git a/talermerchantdemos/survey/templates/footer.html.j2 b/talermerchantdemos/survey/templates/footer.html.j2 deleted file mode 120000 index 028b093..0000000 --- a/talermerchantdemos/survey/templates/footer.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/footer.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/survey/templates/index.html.j2 b/talermerchantdemos/survey/templates/index.html.j2 deleted file mode 100644 index 7c449da..0000000 --- a/talermerchantdemos/survey/templates/index.html.j2 +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "templates/base.html.j2" %} -{% block main %} -
-

- {{ - gettext("Please participate in our survey about payment systems and receive a tip in return.") - }} -

-
-
-
- {{ gettext("Which payment system do you prefer?") }} -
- - -
- -
-
-{% endblock %} diff --git a/talermerchantdemos/survey/templates/language-switcher.html.j2 b/talermerchantdemos/survey/templates/language-switcher.html.j2 deleted file mode 120000 index 6423c5c..0000000 --- a/talermerchantdemos/survey/templates/language-switcher.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/language-switcher.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/survey/templates/menu.html.j2 b/talermerchantdemos/survey/templates/menu.html.j2 deleted file mode 120000 index b9575ea..0000000 --- a/talermerchantdemos/survey/templates/menu.html.j2 +++ /dev/null @@ -1 +0,0 @@ -../../templates/menu.html.j2 \ No newline at end of file diff --git a/talermerchantdemos/survey/translations b/talermerchantdemos/survey/translations deleted file mode 120000 index 0a951f7..0000000 --- a/talermerchantdemos/survey/translations +++ /dev/null @@ -1 +0,0 @@ -../../translations/ \ No newline at end of file diff --git a/talermerchantdemos/templates/blog-article-frame.html.j2 b/talermerchantdemos/templates/blog-article-frame.html.j2 new file mode 100644 index 0000000..1b51c9d --- /dev/null +++ b/talermerchantdemos/templates/blog-article-frame.html.j2 @@ -0,0 +1,20 @@ +{% extends "blog-base.html.j2" %} +{% block main %} + +{{ article_contents | safe }} + +
+ +{% if refundable %} +

+ {{ + gettext("Taler allows merchants to offer refunds to customers.") + + gettext('You can request a refund within the first hour after buying this article.').format(url=url_for('confirm_refund', lang='en', order_id=order_id)) + }} +

+{% else %} +{{ gettext("Taler allows merchants to offer refunds to customers.") }} +{{ gettext("This article can't be refunded anymore.") }} +{% endif %} + +{% endblock main %} diff --git a/talermerchantdemos/templates/blog-article-refunded.html.j2 b/talermerchantdemos/templates/blog-article-refunded.html.j2 new file mode 100644 index 0000000..6a2e139 --- /dev/null +++ b/talermerchantdemos/templates/blog-article-refunded.html.j2 @@ -0,0 +1,18 @@ +{% extends "blog-base.html.j2" %} +{% block main %} + +

{{ gettext("Refunded") }}

+ +

+{{ + gettext("Your payment (order ID {order}) for the article "{article}" has been refunded.").format(order=order_id,article=article_name) +}} +

+ +

+{{ + gettext("You will not be able to read the article until you pay for it again.") +}} +

+ +{% endblock main %} diff --git a/talermerchantdemos/templates/blog-base.html.j2 b/talermerchantdemos/templates/blog-base.html.j2 new file mode 100644 index 0000000..e533bd4 --- /dev/null +++ b/talermerchantdemos/templates/blog-base.html.j2 @@ -0,0 +1,13 @@ +{% extends "common-base.html.j2" %} + +{% block header_content %} + +

{{ gettext("Essay shop") }}

+

{{ + gettext("On this page you can buy articles using an imaginary currency.") + "
" + + gettext("The articles are chapters from Richard Stallman's book "Free Software, Free Society".") + "
" + + gettext('The book is published by the FSF and available gratis at gnu.org.').format(shop="https://shop.fsf.org/product/free-software-free-society-2", gnu="https://www.gnu.org") + }} +

+ +{% endblock %} diff --git a/talermerchantdemos/templates/blog-confirm-refund.html.j2 b/talermerchantdemos/templates/blog-confirm-refund.html.j2 new file mode 100644 index 0000000..ab5dfcd --- /dev/null +++ b/talermerchantdemos/templates/blog-confirm-refund.html.j2 @@ -0,0 +1,23 @@ +{% extends "blog-base.html.j2" %} +{% block main %} +

{{ gettext("Confirm refund request for article") }}

+ +

+ {{ + gettext("Do you want to get a refund for the article {name}?").format(name=article_name) + "
" + + gettext("In this demonstration, refunds will be automatically approved by the merchant.") + "
" + + gettext("After you have obtained a refund, you will not be able to read the article anymore.") + }} +

+ +

+ {{ + gettext ("You will only be able to receive the refund on the same wallet that you have used to pay for this article originally.") + }} +

+ +
+ + +
+{% endblock main %} diff --git a/talermerchantdemos/templates/blog-error.html.j2 b/talermerchantdemos/templates/blog-error.html.j2 new file mode 100644 index 0000000..5b2a381 --- /dev/null +++ b/talermerchantdemos/templates/blog-error.html.j2 @@ -0,0 +1,24 @@ +{% extends "blog-base.html.j2" %} +{% block main %} +

{{ gettext("Error encountered") }}

+ +

{{ message }}

+ + {% if status_code %} +

+ {{ gettext ("The backend returned status code {code}.").format(code=status_code) }}. +

+ {% endif %} + + {% if json %} +

{{gettext("Backend response:")}}

+
{{ json }}
+ {% endif %} + + {% if stack %} +

{{gettext("Stack trace:")}}

+
+    {{ stack }}
+  
+ {% endif %} +{% endblock main %} diff --git a/talermerchantdemos/templates/blog-index.html.j2 b/talermerchantdemos/templates/blog-index.html.j2 new file mode 100644 index 0000000..dd1bced --- /dev/null +++ b/talermerchantdemos/templates/blog-index.html.j2 @@ -0,0 +1,36 @@ +{% extends "blog-base.html.j2" %} +{% block main %} +

{{ gettext("Essay Shop: Free Software, Free Society") }}

+
+

{{ gettext("This is the latest edition of Free Software, Free Society: Selected Essays of Richard M. Stallman.") }}
+ Free Software Foundation
+ 51 Franklin Street, Fifth Floor
+ Boston, MA 02110-1335 +
+ Copyright © 2002, 2010 Free Software Foundation, Inc. +

+ +

{{ gettext("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.") }} +

+

ISBN 978-0-9831592-0-9

+
+ +

{{ gettext("Chapters") }}

+
+ {{ + gettext("Click on an individual chapter to to purchase it with GNU Taler.") + + gettext('You can get free, virtual money to buy articles on this page at the bank').format(env('TALER_ENV_URL_BANK')) + }} +
+ +
+ {% for article in articles %} + + {% else %} + ({{ gettext("No articles available in this language.") }}) + {% endfor %} +
+{% endblock main %} diff --git a/talermerchantdemos/templates/common-base.html.j2 b/talermerchantdemos/templates/common-base.html.j2 new file mode 100644 index 0000000..ff8d655 --- /dev/null +++ b/talermerchantdemos/templates/common-base.html.j2 @@ -0,0 +1,50 @@ + + + + + + {{ gettext("GNU Taler Demo: Introduction") }} + + + + + {% block styles %}{% endblock %} + {% block scripts %}{% endblock %} + + + +
+

+
+

+ +
+ {% block header_content %} +

This is the header content.

+ {% endblock %} +
+
+ {% from 'menu.html.j2' import menu with context %} {{ menu('landing') }} + +
+ {% block main %} + This is the main content of the page. + {% endblock %} + {% include 'footer.html.j2' %} +
+ + diff --git a/talermerchantdemos/templates/donations-base.html.j2 b/talermerchantdemos/templates/donations-base.html.j2 new file mode 100644 index 0000000..b214eac --- /dev/null +++ b/talermerchantdemos/templates/donations-base.html.j2 @@ -0,0 +1,13 @@ +{% extends "common-base.html.j2" %} + +{% block header_content %} + +

+{{gettext("Donations")}}

+

{{ +gettext ("This is the donation page.") + "
" + +gettext ("Using this page you can make donations in {currency} to Free Software projects.").format(currency=merchant_currency) +}} +

+ +{% endblock %} diff --git a/talermerchantdemos/templates/donations-checkout.html.j2 b/talermerchantdemos/templates/donations-checkout.html.j2 new file mode 100644 index 0000000..ec63d9f --- /dev/null +++ b/talermerchantdemos/templates/donations-checkout.html.j2 @@ -0,0 +1,50 @@ +{% extends "donations-base.html.j2" %} + +{% block main %} +
+

{{ gettext("Select your payment method") }}

+ +

+ {{ + gettext('This is an example for a "checkout" page of a Web shop.') + "" + + gettext('On the previous page, you have created the shopping cart and decided which product to buy (i.e. which project to donate KUDOS to).') + "
" + + gettext('As Taler is not yet universally used, we expect merchants will offer various payment options.') + "
" + + gettext('To continue with the demo, select the "Taler" payment option.') + }} +

+

+ {{ + gettext('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.') + "" + + gettext('It would be 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.') + }} +

+ +
+

+ + + + + +
+
+ +
+ +{% endblock main %} diff --git a/talermerchantdemos/templates/donations-error.html.j2 b/talermerchantdemos/templates/donations-error.html.j2 new file mode 100644 index 0000000..8c48335 --- /dev/null +++ b/talermerchantdemos/templates/donations-error.html.j2 @@ -0,0 +1,24 @@ +{% extends "donations-base.html.j2" %} +{% block main %} +

{{ gettext("Error encountered") }}

+ +

{{ message }}

+ + {% if status_code %} +

+ {{ gettext ("The backend returned status code {code}.").format(code=status_code) }}. +

+ {% endif %} + + {% if json %} +

{{gettext("Backend response:")}}

+
{{ json }}
+ {% endif %} + + {% if stack %} +

{{gettext("Stack trace:")}}

+
+    {{ stack }}
+  
+ {% endif %} +{% endblock main %} diff --git a/talermerchantdemos/templates/donations-fulfillment.html.j2 b/talermerchantdemos/templates/donations-fulfillment.html.j2 new file mode 100644 index 0000000..b49b7dc --- /dev/null +++ b/talermerchantdemos/templates/donations-fulfillment.html.j2 @@ -0,0 +1,22 @@ +{% extends "donations-base.html.j2" %} + +{% block main %} +

{{ gettext("Donation Receipt") }}

+

+ {{ + gettext("Thank you, {donor}, for donating {amount} to {receiver}.").format(donor=donation_donor,amount=donation_amount,receiver=donation_receiver) + }} +

+

+ {{ + gettext("Please keep the order identifier {id} as a receipt for your donation.").format(id=order_id) + + gettext('You can show other people that you donated by sharing this link with them.').format(link=request.url) + }} +

+ +

+ {{ + gettext('You can always make another donation.').format(link=url_for('index')) + }} +

+{% endblock main %} diff --git a/talermerchantdemos/templates/donations-index.html.j2 b/talermerchantdemos/templates/donations-index.html.j2 new file mode 100644 index 0000000..25a6768 --- /dev/null +++ b/talermerchantdemos/templates/donations-index.html.j2 @@ -0,0 +1,44 @@ +{% extends "donations-base.html.j2" %} + +{% block main %} +

{{ gettext("Donate to Free Software projects") }}

+ +

+{{ + gettext("This donations website shows the user experience for donations with GNU Taler.") + "
" + + gettext("You can make donations in an toy currency ({currency})").format(currency=merchant_currency) +}} +

+ +
+

+ {{ + gettext("Please select a project, the amount (*) of {currency} you wish to donate, and enter the donor's name that will appear on your receipt:").format(currency=merchant_currency) + }} +

+ +
+
+ + + + +
+
+

+ {{ + gettext("(*) To make the demo a bit more interesting, the 5 {currency} option is deliberately implemented with a fault: the merchant will try to make you donate 6 {currency} instead of the 5 {currency} shown in the form. But do not worry, you will be given the opportunity to review the final offer from the merchant in the Taler wallet. That way, Taler protects you from committing to erroneous payments.").format(currency=merchant_currency) + }} +

+
+ +{% endblock %} diff --git a/talermerchantdemos/templates/donations-provider-not-supported.html.j2 b/talermerchantdemos/templates/donations-provider-not-supported.html.j2 new file mode 100644 index 0000000..a92af77 --- /dev/null +++ b/talermerchantdemos/templates/donations-provider-not-supported.html.j2 @@ -0,0 +1,11 @@ +{% extends "donations-base.html.j2" %} + +{% block main %} +

{{ gettext("Payment Provider Not Supported") }}

+

+{{ + gettext("Unfortunately the selected payment provider is not supported in this demo.") + "
" + + gettext("Please go back and select "Taler".") +}} +

+{% endblock main %} diff --git a/talermerchantdemos/templates/landing-base.html.j2 b/talermerchantdemos/templates/landing-base.html.j2 new file mode 100644 index 0000000..e279767 --- /dev/null +++ b/talermerchantdemos/templates/landing-base.html.j2 @@ -0,0 +1,12 @@ +{% extends "common-base.html.j2" %} + +{% block header_content %} + +

{{ gettext("Introduction") }}

+

{{ + gettext("This is the GNU Taler demo.") + "
" + + gettext("Here you can try out the GNU Taler payment system using a toy currency.") + }} +

+ +{% endblock %} diff --git a/talermerchantdemos/templates/landing-error.html.j2 b/talermerchantdemos/templates/landing-error.html.j2 new file mode 100644 index 0000000..7909656 --- /dev/null +++ b/talermerchantdemos/templates/landing-error.html.j2 @@ -0,0 +1,13 @@ +{% extends "landing-base.html.j2" %} +{% block main %} +

{{ gettext("Error encountered") }}

+ +

{{ message }}

+ + {% if stack %} +

{{gettext("Stack trace:")}}

+
+    {{ stack }}
+  
+ {% endif %} +{% endblock main %} diff --git a/talermerchantdemos/templates/landing-index.html.j2 b/talermerchantdemos/templates/landing-index.html.j2 new file mode 100644 index 0000000..16145e3 --- /dev/null +++ b/talermerchantdemos/templates/landing-index.html.j2 @@ -0,0 +1,89 @@ +{% extends "landing-base.html.j2" %} +{% block main %} +
+

{{ gettext("Step 1: Install the Taler wallet") }}

+

+ {{ + gettext('Install the wallet from the installation page.').format(link="https://wallet.taler.net/") + "
" + + gettext('Installation only takes one click.') + }} +

+

+ {{ gettext('After installation, you may be asked to grant the browser-based Taler wallet additional optional permissions that allow it to improve your user experience.') }}
+ {{ gettext('These permissions will allow the wallet to automatically open if a page asks for a Taler payment.') }}
+ {{ gettext('Regardless of the permissions you grant, the wallet will never transmit information about you or your browsing history to anyone.') }} +

+
+ +
+

{{ gettext("Step 2: Withdraw coins") }}

+

+ {{ + gettext('In this demo you are paying with {currency}, an imaginary currency.').format(currency=merchant_currency) + "
" + + gettext('To withdraw {currency} coins you must first create an account at our bank.').format(currency=merchant_currency,bank=bank_url) + "
" + + gettext('Signing up only requires you to pick a username and password.') + "
" + + gettext('When you create an account at our bank, you will be credited 100 {currency} for signing up.').format(currency=merchant_currency) + "
" + + gettext("Afterwards, use the bank's Web interface to authorize the transfer of {currency} to your wallet.").format(currency=merchant_currency) + }} +

+

+ {{ gettext('Once you have completed this step, you can click on the Taler icon in your browser to check your balance.') }} +

+
+ +
+

{{ gettext("Step 3: Pay") }}

+

+ {{ + gettext("We have two demo merchants where you can spend your coins:") + }} +

+
    +
  • + {{ + gettext('At the essay store you can pay in {currency} for individual chapters of Richard Stallman's book "Free Software, Free Society".').format(blog=merchant_blog_url,currency=merchant_currency) + "
    " + + gettext('The book is also available for free at the FSF.').format(fsf="http://www.fsf.org/") + }} +
  • +
  • + {{ + gettext('At the project donation website you can show respect to a software project of your choice by donating {currency} to them.').format(donations=merchant_donations_url,currency=merchant_currency) + }} +
  • +
+
+
+

{{ gettext("Step 4: Check money flow") }}

+

+ {{ + gettext('You can see the wire transfers from the escrow account of the exchange to the merchants on the public accounts page of the bank.').format(bank=bank_url) + "
" + + gettext('Note that only accounts configured to be publicly viewable are shown on that page.') + }} +

+
+
+

{{ gettext("Step 5: Survey") }}

+

+ {{ + gettext("Websites can give tips to visitors for completing tasks.") + "
" + + gettext('You can earn some {currency} coins by filling in our survey.').format(url=merchant_survey_url,currency=merchant_currency) + }} +

+
+ +
+

{{ gettext("Step 6: Reach out to us") }}

+

+ {{ + gettext("We appreciate feedback about Taler and this demonstrator.") + "
" + + gettext('Let us know what you think by contacting us.').format(link="mailto:demo-feedback@taler.net") + }} +

+
+{% endblock %} diff --git a/talermerchantdemos/templates/menu.html.j2 b/talermerchantdemos/templates/menu.html.j2 index c364a2c..937030d 100644 --- a/talermerchantdemos/templates/menu.html.j2 +++ b/talermerchantdemos/templates/menu.html.j2 @@ -16,10 +16,7 @@ {{gettext("Tipping/Survey")}} - - {% include 'templates/language-switcher.html.j2' %} + {% include 'language-switcher.html.j2' %} {%- endmacro %} diff --git a/talermerchantdemos/templates/survey-base.html.j2 b/talermerchantdemos/templates/survey-base.html.j2 new file mode 100644 index 0000000..f35f514 --- /dev/null +++ b/talermerchantdemos/templates/survey-base.html.j2 @@ -0,0 +1,13 @@ +{% extends "common-base.html.j2" %} + +{% block header_content %} + +

+{{ gettext("Survey") }}

+

{{ +gettext("This page demonstrates how to tip visitors for completing small tasks.") + "
" + +gettext("Tipping is a way for offer cash rewards that go directly into a user's wallet.") +}} +

+ +{% endblock %} diff --git a/talermerchantdemos/templates/survey-error.html.j2 b/talermerchantdemos/templates/survey-error.html.j2 new file mode 100644 index 0000000..b2f84f9 --- /dev/null +++ b/talermerchantdemos/templates/survey-error.html.j2 @@ -0,0 +1,24 @@ +{% extends "survey-base.html.j2" %} +{% block main %} +

{{ gettext("Error encountered") }}

+ +

{{ message }}

+ + {% if status_code %} +

+ {{ gettext ("The backend returned status code {code}.").format(code=status_code) }} +

+ {% endif %} + + {% if json %} +

{{gettext("Backend response:")}}

+
{{ json }}
+ {% endif %} + + {% if stack %} +

{{gettext("Stack trace:")}}

+
+    {{ stack }}
+  
+ {% endif %} +{% endblock main %} diff --git a/talermerchantdemos/templates/survey-index.html.j2 b/talermerchantdemos/templates/survey-index.html.j2 new file mode 100644 index 0000000..e7d323b --- /dev/null +++ b/talermerchantdemos/templates/survey-index.html.j2 @@ -0,0 +1,26 @@ +{% extends "survey-base.html.j2" %} +{% block main %} +
+

+ {{ + gettext("Please participate in our survey about payment systems and receive a tip in return.") + }} +

+
+
+
+ {{ gettext("Which payment system do you prefer?") }} +
+ + +
+ +
+
+{% endblock %} -- cgit v1.2.3