From f7829956ff2ee4b517f010a3f23bc62012e881e7 Mon Sep 17 00:00:00 2001
From: Marcello Stanisci
Date: Mon, 27 May 2019 18:03:08 +0200
Subject: use latest config logic
---
talerdonations/talerconfig.py | 392 ++++++++++++++++++++++++++++++++----------
1 file changed, 304 insertions(+), 88 deletions(-)
(limited to 'talerdonations')
diff --git a/talerdonations/talerconfig.py b/talerdonations/talerconfig.py
index a7ca065..2d2c78e 100644
--- a/talerdonations/talerconfig.py
+++ b/talerdonations/talerconfig.py
@@ -1,22 +1,21 @@
-# This file is part of TALER
-# (C) 2016 INRIA
+##
+# This file is part of TALER
+# (C) 2016 INRIA
#
-# TALER is free software; you can redistribute it and/or modify it under the
-# terms of the GNU Affero General Public License as published by the Free Software
-# Foundation; either version 3, or (at your option) any later version.
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Affero General Public License as published by the Free Software
+# Foundation; either version 3, or (at your option) any later version.
#
-# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License along with
-# TALER; see the file COPYING. If not, see
+# You should have received a copy of the GNU General Public License along with
+# TALER; see the file COPYING. If not, see
#
-# @author Florian Dold
-
-"""
-Parse GNUnet-style configurations in pure Python
-"""
+# @author Florian Dold
+# @author Marcello Stanisci
+# @brief Parse GNUnet-style configurations in pure Python
import logging
import collections
@@ -24,34 +23,48 @@ import os
import weakref
import sys
import re
+from typing import Callable, Any
LOGGER = logging.getLogger(__name__)
__all__ = ["TalerConfig"]
TALER_DATADIR = None
+
try:
- # not clear if this is a good idea ...
from talerpaths import TALER_DATADIR as t
TALER_DATADIR = t
except ImportError:
pass
+##
+# Exception class for a any configuration error.
class ConfigurationError(Exception):
pass
+##
+# Exception class for malformed strings having with parameter
+# expansion.
class ExpansionSyntaxError(Exception):
pass
-
-def expand(var, getter):
- """
- Do shell-style parameter expansion.
- Supported syntax:
- - ${X}
- - ${X:-Y}
- - $X
- """
+##
+# Do shell-style parameter expansion.
+# Supported syntax:
+# - ${X}
+# - ${X:-Y}
+# - $X
+#
+# @param var entire config value that might contain a parameter
+# to expand.
+# @param getter function that is in charge of returning _some_
+# value to be used in place of the parameter to expand.
+# Typically, the replacement is searched first under the
+# PATHS section of the current configuration, or (if not
+# found) in the environment.
+#
+# @return the expanded config value.
+def expand(var: str, getter: Callable[[str], str]) -> str:
pos = 0
result = ""
while pos != -1:
@@ -88,38 +101,25 @@ def expand(var, getter):
result = result + replace
pos = end
-
return result + var[pos:]
-
-class OptionDict(collections.defaultdict):
- def __init__(self, config, section_name):
- self.config = weakref.ref(config)
- self.section_name = section_name
- super().__init__()
- def __missing__(self, key):
- entry = Entry(self.config(), self.section_name, key)
- self[key] = entry
- return entry
- def __getitem__(self, chunk):
- return super().__getitem__(chunk.lower())
- def __setitem__(self, chunk, value):
- super().__setitem__(chunk.lower(), value)
-
-
-class SectionDict(collections.defaultdict):
- def __missing__(self, key):
- value = OptionDict(self, key)
- self[key] = value
- return value
- def __getitem__(self, chunk):
- return super().__getitem__(chunk.lower())
- def __setitem__(self, chunk, value):
- super().__setitem__(chunk.lower(), value)
-
-
+##
+# A configuration entry.
class Entry:
- def __init__(self, config, section, option, **kwargs):
+
+ ##
+ # Init constructor.
+ #
+ # @param self the object itself.
+ # @param config reference to a configuration object - FIXME
+ # define "configuration object".
+ # @param section name of the config section where this entry
+ # got defined.
+ # @param option name of the config option associated with this
+ # entry.
+ # @param kwargs keyword arguments that hold the value / filename
+ # / line number of this current option.
+ def __init__(self, config, section: str, option: str, **kwargs) -> None:
self.value = kwargs.get("value")
self.filename = kwargs.get("filename")
self.lineno = kwargs.get("lineno")
@@ -127,14 +127,36 @@ class Entry:
self.option = option
self.config = weakref.ref(config)
- def __repr__(self):
+ ##
+ # XML representation of this entry.
+ #
+ # @param self the object itself.
+ # @return XML string holding all the relevant information
+ # for this entry.
+ def __repr__(self) -> str:
return "" \
% (self.section, self.option, repr(self.value),)
- def __str__(self):
+ ##
+ # Return the value for this entry, as is.
+ #
+ # @param self the object itself.
+ # @return the config value.
+ def __str__(self) -> Any:
return self.value
- def value_string(self, default=None, required=False, warn=False):
+ ##
+ # Return entry value, accepting defaults.
+ #
+ # @param self the object itself
+ # @param default default value to return if none was found.
+ # @param required indicate whether the value was required or not.
+ # If the value was required, but was not found, an exception
+ # is found.
+ # @param warn if True, outputs a warning message if the value was
+ # not found -- regardless of it being required or not.
+ # @return the value, or the given @a default, if not found.
+ def value_string(self, default=None, required=False, warn=False) -> str:
if required and self.value is None:
raise ConfigurationError("Missing required option '%s' in section '%s'" \
% (self.option.upper(), self.section.upper()))
@@ -149,7 +171,16 @@ class Entry:
return default
return self.value
- def value_int(self, default=None, required=False, warn=False):
+ ##
+ # Return entry value as a _int_. Raise exception if the
+ # value is not convertible to a integer.
+ #
+ # @param self the object itself
+ # @param default currently ignored.
+ # @param required currently ignored.
+ # @param warn currently ignored.
+ # @return the value, or the given @a default, if not found.
+ def value_int(self, default=None, required=False, warn=False) -> int:
value = self.value_string(default, warn, required)
if value is None:
return None
@@ -158,8 +189,13 @@ class Entry:
except ValueError:
raise ConfigurationError("Expected number for option '%s' in section '%s'" \
% (self.option.upper(), self.section.upper()))
-
- def _getsubst(self, key):
+ ##
+ # Fetch value to substitute to expansion variables.
+ #
+ # @param self the object itself.
+ # @param key the value's name to lookup.
+ # @return the value, if found, None otherwise.
+ def _getsubst(self, key: str) -> Any:
value = self.config()["paths"][key].value
if value is not None:
return value
@@ -168,31 +204,136 @@ class Entry:
return value
return None
- def value_filename(self, default=None, required=False, warn=False):
+ ##
+ # Fetch the config value that should be a filename,
+ # taking care of invoking the variable-expansion logic first.
+ #
+ # @param self the object itself.
+ # @param default currently ignored.
+ # @param required currently ignored.
+ # @param warn currently ignored.
+ # @return the (expanded) filename.
+ def value_filename(self, default=None, required=False, warn=False) -> str:
value = self.value_string(default, required, warn)
if value is None:
return None
return expand(value, self._getsubst)
- def location(self):
+ ##
+ # Give the filename and line number of this config entry.
+ #
+ # @param self this object.
+ # @return :, or "" if one
+ # is not known.
+ def location(self) -> str:
if self.filename is None or self.lineno is None:
return ""
return "%s:%s" % (self.filename, self.lineno)
+##
+# Represent a section by inheriting from 'defaultdict'.
+class OptionDict(collections.defaultdict):
+
+ ##
+ # Init constructor.
+ #
+ # @param self the object itself
+ # @param config the "config" object -- typically a @a TalerConfig instance.
+ # @param section_name the section name to assign to this object.
+ def __init__(self, config, section_name: str) -> None:
+ self.config = weakref.ref(config)
+ self.section_name = section_name
+ super().__init__()
+
+ ##
+ # Logic to run when a non-existent key is dereferenced.
+ # Just create and return a empty config @a Entry. Note
+ # that the freshly created entry will nonetheless put
+ # under the accessed key (that *does* become existent
+ # afterwards).
+ #
+ # @param self the object itself.
+ # @param key the key attempted to be accessed.
+ # @return the no-value entry.
+ def __missing__(self, key: str) -> Entry:
+ entry = Entry(self.config(), self.section_name, key)
+ self[key] = entry
+ return entry
+
+ ##
+ # Attempt to fetch one value from the object.
+ #
+ # @param self the object itself.
+ # @param chunk the key (?) that is tried to access.
+ # @return the object, if it exists, or a freshly created
+ # (empty) one, if it doesn't exist.
+ def __getitem__(self, chunk: str) -> Entry:
+ return super().__getitem__(chunk.lower())
+
+ ##
+ # Set one value into the object.
+ #
+ # @param self the object itself.
+ # @param chunk key under which the value is going to be set.
+ # @param value value to set the @a chunk to.
+ def __setitem__(self, chunk: str, value: Entry) -> None:
+ super().__setitem__(chunk.lower(), value)
+
+##
+# Collection of all the (@a OptionDict) sections.
+class SectionDict(collections.defaultdict):
+ ##
+ # Automatically invoked when a missing section is
+ # dereferenced. It creates the missing - empty - section.
+ #
+ # @param self the object itself.
+ # @param key the dereferenced section name.
+ # @return the freshly created section.
+ def __missing__(self, key):
+ value = OptionDict(self, key)
+ self[key] = value
+ return value
+
+ ##
+ # Attempt to retrieve a section.
+ #
+ # @param self the object itself.
+ # @param chunk the section name.
+ def __getitem__(self, chunk: str) -> OptionDict:
+ return super().__getitem__(chunk.lower())
+
+ ##
+ # Set a section.
+ #
+ # @param self the object itself.
+ # @param chunk the section name to set.
+ # @param value the value to set under that @a chunk.
+ def __setitem__(self, chunk: str, value: OptionDict) -> None:
+ super().__setitem__(chunk.lower(), value)
+
+##
+# One loaded taler configuration, including base configuration
+# files and included files.
class TalerConfig:
- """
- One loaded taler configuration, including base configuration
- files and included files.
- """
- def __init__(self):
- """
- Initialize an empty configuration
- """
- self.sections = SectionDict()
-
- # defaults != config file: the first is the 'base'
- # whereas the second overrides things from the first.
+
+ ##
+ # Init constructor..
+ #
+ # @param self the object itself.
+ def __init__(self) -> None:
+ self.sections = SectionDict() # just plain dict
+
+ ##
+ # Load a configuration file, instantiating a config object.
+ #
+ # @param filename the filename where to load the configuration
+ # from. If None, it defaults "taler.conf".
+ # @param load_defaults if True, then defaults values are loaded
+ # (from canonical directories like "/share/config.d/taler/")
+ # before the actual configuration file. This latter then
+ # can override some/all the defaults.
+ # @return the config object.
@staticmethod
def from_file(filename=None, load_defaults=True):
cfg = TalerConfig()
@@ -204,22 +345,57 @@ class TalerConfig:
filename = os.path.expanduser("~/.config/taler.conf")
if load_defaults:
cfg.load_defaults()
- cfg.load_file(filename)
+ cfg.load_file(os.path.expanduser(filename))
return cfg
- def value_string(self, section, option, **kwargs):
+ ##
+ # Get a string value from the config.
+ #
+ # @param self the config object itself.
+ # @param section the section to fetch the value from.
+ # @param option the value's option name.
+ # @param kwargs dict argument with instructions about
+ # the value retrieval logic.
+ # @return the wanted string (or a default / exception if
+ # a error occurs).
+ def value_string(self, section, option, **kwargs) -> str:
return self.sections[section][option].value_string(
kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
- def value_filename(self, section, option, **kwargs):
+ ##
+ # Get a value from the config that should be a filename.
+ # The variable expansion for the path's components is internally managed.
+ #
+ # @param self the config object itself.
+ # @param section the section to fetch the value from.
+ # @param option the value's option name.
+ # @param kwargs dict argument with instructions about
+ # the value retrieval logic.
+ # @return the wanted filename (or a default / exception if
+ # a error occurs).
+ def value_filename(self, section, option, **kwargs) -> str:
return self.sections[section][option].value_filename(
kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
- def value_int(self, section, option, **kwargs):
+ ##
+ # Get a integer value from the config.
+ #
+ # @param self the config object itself.
+ # @param section the section to fetch the value from.
+ # @param option the value's option name.
+ # @param kwargs dict argument with instructions about
+ # the value retrieval logic.
+ # @return the wanted integer (or a default / exception if
+ # a error occurs).
+ def value_int(self, section, option, **kwargs) -> int:
return self.sections[section][option].value_int(
kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
- def load_defaults(self):
+ ##
+ # Load default values from canonical locations.
+ #
+ # @param self the object itself.
+ def load_defaults(self) -> None:
base_dir = os.environ.get("TALER_BASE_CONFIG")
if base_dir:
self.load_dir(base_dir)
@@ -236,16 +412,25 @@ class TalerConfig:
return
LOGGER.warning("no base directory found")
+ ##
+ # Load configuration from environment variable
+ # TALER_CONFIG_FILE or from default location if the
+ # variable is not set.
+ #
+ # @param args currently unused.
+ # @param kwargs kwargs for subroutine @a from_file.
+ # @return freshly instantiated config object.
@staticmethod
def from_env(*args, **kwargs):
- """
- Load configuration from environment variable TALER_CONFIG_FILE
- or from default location if the variable is not set.
- """
filename = os.environ.get("TALER_CONFIG_FILE")
return TalerConfig.from_file(filename, *args, **kwargs)
- def load_dir(self, dirname):
+ ##
+ # Load config values from _each_ file found in a directory.
+ #
+ # @param self the object itself.
+ # @param dirname the directory to crawl in the look for config files.
+ def load_dir(self, dirname) -> None:
try:
files = os.listdir(dirname)
except FileNotFoundError:
@@ -256,7 +441,11 @@ class TalerConfig:
continue
self.load_file(os.path.join(dirname, file))
- def load_file(self, filename):
+ ##
+ # Load config values from a file.
+ #
+ # @param filename config file to take the values from.
+ def load_file(self, filename) -> None:
sections = self.sections
try:
with open(filename, "r") as file:
@@ -271,6 +460,16 @@ class TalerConfig:
if line.startswith("#"):
# comment
continue
+ if line.startswith("@INLINE@"):
+ pair = line.split()
+ if 2 != len(pair):
+ LOGGER.error("invalid inlined config filename given ('%s')" % line)
+ continue
+ if pair[1].startswith("/"):
+ self.load_file(pair[1])
+ else:
+ self.load_file(os.path.join(os.path.dirname(filename), pair[1]))
+ continue
if line.startswith("["):
if not line.endswith("]"):
LOGGER.error("invalid section header in line %s: %s",
@@ -299,8 +498,16 @@ class TalerConfig:
LOGGER.error("Configuration file (%s) not found", filename)
sys.exit(3)
-
- def dump(self):
+ ##
+ # Dump the textual representation of a config object.
+ #
+ # Format:
+ #
+ # [section]
+ # option = value # FIXME (what is location?)
+ #
+ # @param self the object itself, that will be dumped.
+ def dump(self) -> None:
for kv_section in self.sections.items():
print("[%s]" % (kv_section[1].section_name,))
for kv_option in kv_section[1].items():
@@ -309,7 +516,16 @@ class TalerConfig:
kv_option[1].value,
kv_option[1].location()))
- def __getitem__(self, chunk):
+
+ ##
+ # Return a whole section from this object.
+ #
+ # @param self the object itself.
+ # @param chunk name of the section to return.
+ # @return the section - note that if the section is
+ # not found, a empty one will created on the fly,
+ # then set under 'chunk', and returned.
+ def __getitem__(self, chunk: str) -> OptionDict:
if isinstance(chunk, str):
return self.sections[chunk]
raise TypeError("index must be string")
--
cgit v1.2.3
From 5cdf3b0799b848b0b9f547b49b7b57f6c27df372 Mon Sep 17 00:00:00 2001
From: Marcello Stanisci
Date: Mon, 27 May 2019 18:08:22 +0200
Subject: ux
---
talerdonations/talerconfig.py | 2 ++
1 file changed, 2 insertions(+)
(limited to 'talerdonations')
diff --git a/talerdonations/talerconfig.py b/talerdonations/talerconfig.py
index 2d2c78e..9598be5 100644
--- a/talerdonations/talerconfig.py
+++ b/talerdonations/talerconfig.py
@@ -32,6 +32,7 @@ __all__ = ["TalerConfig"]
TALER_DATADIR = None
try:
+ # not clear if this is a good idea ...
from talerpaths import TALER_DATADIR as t
TALER_DATADIR = t
except ImportError:
@@ -343,6 +344,7 @@ class TalerConfig:
filename = os.path.join(xdg, "taler.conf")
else:
filename = os.path.expanduser("~/.config/taler.conf")
+ logging.info("Loading default config: (%s)" % filename)
if load_defaults:
cfg.load_defaults()
cfg.load_file(os.path.expanduser(filename))
--
cgit v1.2.3
From dd835689654c18353b02f9f5809c7746de222ab9 Mon Sep 17 00:00:00 2001
From: Marcello Stanisci
Date: Mon, 27 May 2019 18:17:12 +0200
Subject: talerconfig
---
talerdonations/talerconfig.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
(limited to 'talerdonations')
diff --git a/talerdonations/talerconfig.py b/talerdonations/talerconfig.py
index 9598be5..4a44c97 100644
--- a/talerdonations/talerconfig.py
+++ b/talerdonations/talerconfig.py
@@ -344,7 +344,7 @@ class TalerConfig:
filename = os.path.join(xdg, "taler.conf")
else:
filename = os.path.expanduser("~/.config/taler.conf")
- logging.info("Loading default config: (%s)" % filename)
+ print("Loading default config: (%s)" % filename)
if load_defaults:
cfg.load_defaults()
cfg.load_file(os.path.expanduser(filename))
@@ -497,7 +497,8 @@ class TalerConfig:
value=value, filename=filename, lineno=lineno)
sections[current_section][key] = entry
except FileNotFoundError:
- LOGGER.error("Configuration file (%s) not found", filename)
+ # not logging here, as this interests the final user mostly.
+ print("Configuration file (%s) not found" % filename)
sys.exit(3)
##
--
cgit v1.2.3
From 455755737e6dcb7f938f7e6f9672bfc3e38f0a73 Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:01:43 +0200
Subject: yapf style file
---
.style.yapf | 5 ++
talerdonations/donations/donations.py | 76 ++++++++++++++++------------
talerdonations/talerconfig.py | 95 ++++++++++++++++++++++++-----------
talerdonations/tests.py | 1 +
4 files changed, 116 insertions(+), 61 deletions(-)
create mode 100644 .style.yapf
(limited to 'talerdonations')
diff --git a/.style.yapf b/.style.yapf
new file mode 100644
index 0000000..3b39780
--- /dev/null
+++ b/.style.yapf
@@ -0,0 +1,5 @@
+[style]
+based_on_style = pep8
+coalesce_brackets=True
+column_limit=80
+dedent_closing_brackets=True
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index f0f97c5..5655734 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -53,6 +53,7 @@ app.config.from_object(__name__)
def utility_processor():
def env(name, default=None):
return os.environ.get(name, default)
+
return dict(env=env)
@@ -76,7 +77,9 @@ def err_abort(abort_status_code, **params):
def backend_get(endpoint, params):
headers = {"Authorization": "ApiKey " + APIKEY}
try:
- resp = requests.get(urljoin(BACKEND_URL, endpoint), params=params, headers=headers)
+ resp = requests.get(urljoin(BACKEND_URL, endpoint),
+ params=params,
+ headers=headers)
except requests.ConnectionError:
err_abort(500, message="Could not establish connection to backend")
try:
@@ -84,8 +87,10 @@ def backend_get(endpoint, params):
except ValueError:
err_abort(500, message="Could not parse response from backend")
if resp.status_code != 200:
- err_abort(500, message="Backend returned error status",
- json=response_json, status_code=resp.status_code)
+ err_abort(500,
+ message="Backend returned error status",
+ json=response_json,
+ status_code=resp.status_code)
return response_json
@@ -100,21 +105,25 @@ def backend_get(endpoint, params):
def backend_post(endpoint, json):
headers = {"Authorization": "ApiKey " + APIKEY}
try:
- resp = requests.post(urljoin(BACKEND_URL, endpoint), json=json, headers=headers)
+ resp = requests.post(urljoin(BACKEND_URL, endpoint),
+ json=json,
+ headers=headers)
except requests.ConnectionError:
err_abort(500, message="Could not establish connection to backend")
try:
response_json = resp.json()
except ValueError:
- err_abort(500, message="Could not parse response from backend",
+ err_abort(500,
+ message="Could not parse response from backend",
status_code=resp.status_code)
if resp.status_code != 200:
- err_abort(500, message="Backend returned error status",
- json=response_json, status_code=resp.status_code)
+ err_abort(500,
+ message="Backend returned error status",
+ json=response_json,
+ status_code=resp.status_code)
return response_json
-
##
# Inspect GET arguments in the look for a parameter.
#
@@ -126,6 +135,7 @@ def expect_parameter(name):
return err_abort(400, message="parameter '{}' required".format(name))
return val
+
##
# "Fallback" exception handler to capture all the unmanaged errors.
#
@@ -138,13 +148,16 @@ def internal_error(e):
message="Internal error",
stack=traceback.format_exc())
+
##
# Serve the main index page.
#
# @return response object of the index page.
@app.route("/")
def index():
- return flask.render_template("templates/index.html", merchant_currency=CURRENCY)
+ return flask.render_template("templates/index.html",
+ merchant_currency=CURRENCY)
+
##
# Serve the "/javascript" page.
@@ -155,7 +168,6 @@ 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,
@@ -167,12 +179,11 @@ def checkout():
amount = expect_parameter("donation_amount")
donation_receiver = expect_parameter("donation_receiver")
donation_donor = expect_parameter("donation_donor")
- return flask.render_template(
- "templates/checkout.html",
- donation_amount=amount,
- donation_receiver=donation_receiver,
- donation_donor=donation_donor,
- merchant_currency=CURRENCY)
+ return flask.render_template("templates/checkout.html",
+ donation_amount=amount,
+ donation_receiver=donation_receiver,
+ donation_donor=donation_donor,
+ merchant_currency=CURRENCY)
##
@@ -182,8 +193,7 @@ def checkout():
# @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")
-
+ return flask.render_template("templates/provider-not-supported.html")
##
@@ -201,18 +211,24 @@ def donate():
payment_system = expect_parameter("payment_system")
if payment_system != "taler":
return flask.redirect(flask.url_for("provider_not_supported"))
- fulfillment_url = flask.url_for("fulfillment", receiver=donation_receiver, _external=True)
+ fulfillment_url = flask.url_for("fulfillment",
+ receiver=donation_receiver,
+ _external=True)
order = dict(
amount=donation_amount,
- extra=dict(donor=donation_donor, receiver=donation_receiver, amount=donation_amount),
+ extra=dict(donor=donation_donor,
+ receiver=donation_receiver,
+ amount=donation_amount),
fulfillment_url=fulfillment_url,
instance=donation_receiver,
summary="Donation to {}".format(donation_receiver),
)
order_resp = backend_post("order", dict(order=order))
order_id = order_resp["order_id"]
- return flask.redirect(flask.url_for("fulfillment", receiver=donation_receiver, order_id=order_id))
-
+ return flask.redirect(
+ flask.url_for("fulfillment",
+ receiver=donation_receiver,
+ order_id=order_id))
##
@@ -226,8 +242,7 @@ def donate():
@app.route("/donation/")
def fulfillment(receiver):
order_id = expect_parameter("order_id")
- pay_params = dict(instance=receiver,
- order_id=order_id)
+ pay_params = dict(instance=receiver, order_id=order_id)
pay_status = backend_get("check-payment", pay_params)
if pay_status.get("payment_redirect_url"):
@@ -235,13 +250,12 @@ def fulfillment(receiver):
if pay_status.get("paid"):
extra = pay_status["contract_terms"]["extra"]
- return flask.render_template(
- "templates/fulfillment.html",
- donation_receiver=extra["receiver"],
- donation_amount=extra["amount"],
- donation_donor=extra["donor"],
- order_id=order_id,
- currency=CURRENCY)
+ return flask.render_template("templates/fulfillment.html",
+ donation_receiver=extra["receiver"],
+ donation_amount=extra["amount"],
+ donation_donor=extra["donor"],
+ order_id=order_id,
+ currency=CURRENCY)
# no pay_redirect but article not paid, this should never happen!
err_abort(500, message="Internal error, invariant failed", json=pay_status)
diff --git a/talerdonations/talerconfig.py b/talerdonations/talerconfig.py
index 4a44c97..7959ec2 100644
--- a/talerdonations/talerconfig.py
+++ b/talerdonations/talerconfig.py
@@ -38,17 +38,20 @@ try:
except ImportError:
pass
+
##
# Exception class for a any configuration error.
class ConfigurationError(Exception):
pass
+
##
# Exception class for malformed strings having with parameter
# expansion.
class ExpansionSyntaxError(Exception):
pass
+
##
# Do shell-style parameter expansion.
# Supported syntax:
@@ -80,7 +83,7 @@ def expand(var: str, getter: Callable[[str], str]) -> str:
end += 1
if balance != 0:
raise ExpansionSyntaxError("unbalanced parentheses")
- piece = var[start+2:end-1]
+ piece = var[start + 2:end - 1]
if piece.find(":-") > 0:
varname, alt = piece.split(":-", 1)
replace = getter(varname)
@@ -93,9 +96,9 @@ def expand(var: str, getter: Callable[[str], str]) -> str:
replace = var[start:end]
else:
end = start + 2
- while end < len(var) and var[start+1:end+1].isalnum():
+ while end < len(var) and var[start + 1:end + 1].isalnum():
end += 1
- varname = var[start+1:end]
+ varname = var[start + 1:end]
replace = getter(varname)
if replace is None:
replace = var[start:end]
@@ -104,6 +107,7 @@ def expand(var: str, getter: Callable[[str], str]) -> str:
return result + var[pos:]
+
##
# A configuration entry.
class Entry:
@@ -164,11 +168,14 @@ class Entry:
if self.value is None:
if warn:
if default is not None:
- LOGGER.warning("Configuration is missing option '%s' in section '%s',\
- falling back to '%s'", self.option, self.section, default)
+ LOGGER.warning(
+ "Configuration is missing option '%s' in section '%s',\
+ falling back to '%s'", self.option,
+ self.section, default)
else:
- LOGGER.warning("Configuration ** is missing option '%s' in section '%s'",
- self.option.upper(), self.section.upper())
+ LOGGER.warning(
+ "Configuration ** is missing option '%s' in section '%s'",
+ self.option.upper(), self.section.upper())
return default
return self.value
@@ -190,6 +197,7 @@ class Entry:
except ValueError:
raise ConfigurationError("Expected number for option '%s' in section '%s'" \
% (self.option.upper(), self.section.upper()))
+
##
# Fetch value to substitute to expansion variables.
#
@@ -231,6 +239,7 @@ class Entry:
return ""
return "%s:%s" % (self.filename, self.lineno)
+
##
# Represent a section by inheriting from 'defaultdict'.
class OptionDict(collections.defaultdict):
@@ -280,6 +289,7 @@ class OptionDict(collections.defaultdict):
def __setitem__(self, chunk: str, value: Entry) -> None:
super().__setitem__(chunk.lower(), value)
+
##
# Collection of all the (@a OptionDict) sections.
class SectionDict(collections.defaultdict):
@@ -313,6 +323,7 @@ class SectionDict(collections.defaultdict):
def __setitem__(self, chunk: str, value: OptionDict) -> None:
super().__setitem__(chunk.lower(), value)
+
##
# One loaded taler configuration, including base configuration
# files and included files.
@@ -323,7 +334,7 @@ class TalerConfig:
#
# @param self the object itself.
def __init__(self) -> None:
- self.sections = SectionDict() # just plain dict
+ self.sections = SectionDict() # just plain dict
##
# Load a configuration file, instantiating a config object.
@@ -465,36 +476,49 @@ class TalerConfig:
if line.startswith("@INLINE@"):
pair = line.split()
if 2 != len(pair):
- LOGGER.error("invalid inlined config filename given ('%s')" % line)
- continue
+ LOGGER.error(
+ "invalid inlined config filename given ('%s')"
+ % line)
+ continue
if pair[1].startswith("/"):
self.load_file(pair[1])
else:
- self.load_file(os.path.join(os.path.dirname(filename), pair[1]))
+ self.load_file(
+ os.path.join(os.path.dirname(filename),
+ pair[1]))
continue
if line.startswith("["):
if not line.endswith("]"):
- LOGGER.error("invalid section header in line %s: %s",
- lineno, repr(line))
+ LOGGER.error(
+ "invalid section header in line %s: %s",
+ lineno, repr(line))
section_name = line.strip("[]").strip().strip('"')
current_section = section_name
continue
if current_section is None:
- LOGGER.error("option outside of section in line %s: %s", lineno, repr(line))
+ LOGGER.error(
+ "option outside of section in line %s: %s", lineno,
+ repr(line))
continue
pair = line.split("=", 1)
if len(pair) != 2:
- LOGGER.error("invalid option in line %s: %s", lineno, repr(line))
+ LOGGER.error("invalid option in line %s: %s", lineno,
+ repr(line))
key = pair[0].strip()
value = pair[1].strip()
if value.startswith('"'):
value = value[1:]
if not value.endswith('"'):
- LOGGER.error("mismatched quotes in line %s: %s", lineno, repr(line))
+ LOGGER.error("mismatched quotes in line %s: %s",
+ lineno, repr(line))
else:
value = value[:-1]
- entry = Entry(self.sections, current_section, key,
- value=value, filename=filename, lineno=lineno)
+ entry = Entry(self.sections,
+ current_section,
+ key,
+ value=value,
+ filename=filename,
+ lineno=lineno)
sections[current_section][key] = entry
except FileNotFoundError:
# not logging here, as this interests the final user mostly.
@@ -503,23 +527,22 @@ class TalerConfig:
##
# Dump the textual representation of a config object.
- #
+ #
# Format:
- #
+ #
# [section]
# option = value # FIXME (what is location?)
#
# @param self the object itself, that will be dumped.
def dump(self) -> None:
for kv_section in self.sections.items():
- print("[%s]" % (kv_section[1].section_name,))
+ print("[%s]" % (kv_section[1].section_name, ))
for kv_option in kv_section[1].items():
print("%s = %s # %s" % \
(kv_option[1].option,
kv_option[1].value,
kv_option[1].location()))
-
##
# Return a whole section from this object.
#
@@ -538,14 +561,26 @@ if __name__ == "__main__":
import argparse
PARSER = argparse.ArgumentParser()
- PARSER.add_argument("--section", "-s", dest="section",
- default=None, metavar="SECTION")
- PARSER.add_argument("--option", "-o", dest="option",
- default=None, metavar="OPTION")
- PARSER.add_argument("--config", "-c", dest="config",
- default=None, metavar="FILE")
- PARSER.add_argument("--filename", "-f", dest="expand_filename",
- default=False, action='store_true')
+ PARSER.add_argument("--section",
+ "-s",
+ dest="section",
+ default=None,
+ metavar="SECTION")
+ PARSER.add_argument("--option",
+ "-o",
+ dest="option",
+ default=None,
+ metavar="OPTION")
+ PARSER.add_argument("--config",
+ "-c",
+ dest="config",
+ default=None,
+ metavar="FILE")
+ PARSER.add_argument("--filename",
+ "-f",
+ dest="expand_filename",
+ default=False,
+ action='store_true')
ARGS = PARSER.parse_args()
TC = TalerConfig.from_file(ARGS.config)
diff --git a/talerdonations/tests.py b/talerdonations/tests.py
index c9918c2..2e95b60 100644
--- a/talerdonations/tests.py
+++ b/talerdonations/tests.py
@@ -27,6 +27,7 @@ from talerdonations.talerconfig import TalerConfig
TC = TalerConfig.from_env()
CURRENCY = TC["taler"]["currency"].value_string(required=True)
+
##
# Main class that gathers all the tests.
class DonationsTestCase(unittest.TestCase):
--
cgit v1.2.3
From 9c282aa69a4d25f186e591a075b0f2830b5ec45a Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:02:31 +0200
Subject: make pretty
---
setup.py | 51 +++++++++--------
talerdonations/donations/donations.py | 100 ++++++++++++++++++++--------------
talerdonations/talerconfig.py | 95 ++++++++++++++++++--------------
3 files changed, 136 insertions(+), 110 deletions(-)
(limited to 'talerdonations')
diff --git a/setup.py b/setup.py
index 71526a9..906567c 100755
--- a/setup.py
+++ b/setup.py
@@ -1,28 +1,27 @@
from setuptools import setup, find_packages
-setup(name='talerdonations',
- version='0.6.0pre1',
- description='Example donations site for GNU Taler',
- url='git://taler.net/donations',
- author=['Marcello Stanisci', 'Florian Dold'],
- author_email=['stanisci.m@gmail.com', 'dold@taler.net'],
- license='GPL',
- packages=find_packages(),
- install_requires=["Flask>=0.10",
- "requests",
- "uwsgi",
- "jsmin"],
- tests_require=["mock", "nose"],
- test_suite="nose.collector",
- package_data={
- '':[
- "donations/templates/*.html",
- "donations/static/*.svg",
- "donations/static/*.css",
- "donations/static/web-common/*.png",
- "donations/static/web-common/*.css",
- "donations/static/web-common/*.html",
- ]
- },
- scripts=['./bin/taler-merchant-donations'],
- zip_safe=False)
+setup(
+ name='talerdonations',
+ version='0.6.0pre1',
+ description='Example donations site for GNU Taler',
+ url='git://taler.net/donations',
+ author=['Marcello Stanisci', 'Florian Dold'],
+ author_email=['stanisci.m@gmail.com', 'dold@taler.net'],
+ license='GPL',
+ packages=find_packages(),
+ install_requires=["Flask>=0.10", "requests", "uwsgi", "jsmin"],
+ tests_require=["mock", "nose"],
+ test_suite="nose.collector",
+ package_data={
+ '': [
+ "donations/templates/*.html",
+ "donations/static/*.svg",
+ "donations/static/*.css",
+ "donations/static/web-common/*.png",
+ "donations/static/web-common/*.css",
+ "donations/static/web-common/*.html",
+ ]
+ },
+ scripts=['./bin/taler-merchant-donations'],
+ zip_safe=False
+)
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index 5655734..220fbb7 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -77,9 +77,9 @@ def err_abort(abort_status_code, **params):
def backend_get(endpoint, params):
headers = {"Authorization": "ApiKey " + APIKEY}
try:
- resp = requests.get(urljoin(BACKEND_URL, endpoint),
- params=params,
- headers=headers)
+ resp = requests.get(
+ urljoin(BACKEND_URL, endpoint), params=params, headers=headers
+ )
except requests.ConnectionError:
err_abort(500, message="Could not establish connection to backend")
try:
@@ -87,10 +87,12 @@ def backend_get(endpoint, params):
except ValueError:
err_abort(500, message="Could not parse response from backend")
if resp.status_code != 200:
- err_abort(500,
- message="Backend returned error status",
- json=response_json,
- status_code=resp.status_code)
+ err_abort(
+ 500,
+ message="Backend returned error status",
+ json=response_json,
+ status_code=resp.status_code
+ )
return response_json
@@ -105,22 +107,26 @@ def backend_get(endpoint, params):
def backend_post(endpoint, json):
headers = {"Authorization": "ApiKey " + APIKEY}
try:
- resp = requests.post(urljoin(BACKEND_URL, endpoint),
- json=json,
- headers=headers)
+ resp = requests.post(
+ urljoin(BACKEND_URL, endpoint), json=json, headers=headers
+ )
except requests.ConnectionError:
err_abort(500, message="Could not establish connection to backend")
try:
response_json = resp.json()
except ValueError:
- err_abort(500,
- message="Could not parse response from backend",
- status_code=resp.status_code)
+ err_abort(
+ 500,
+ message="Could not parse response from backend",
+ status_code=resp.status_code
+ )
if resp.status_code != 200:
- err_abort(500,
- message="Backend returned error status",
- json=response_json,
- status_code=resp.status_code)
+ err_abort(
+ 500,
+ message="Backend returned error status",
+ json=response_json,
+ status_code=resp.status_code
+ )
return response_json
@@ -144,9 +150,11 @@ def expect_parameter(name):
# (and execution stack!).
@app.errorhandler(Exception)
def internal_error(e):
- return flask.render_template("templates/error.html",
- message="Internal error",
- stack=traceback.format_exc())
+ return flask.render_template(
+ "templates/error.html",
+ message="Internal error",
+ stack=traceback.format_exc()
+ )
##
@@ -155,8 +163,9 @@ def internal_error(e):
# @return response object of the index page.
@app.route("/")
def index():
- return flask.render_template("templates/index.html",
- merchant_currency=CURRENCY)
+ return flask.render_template(
+ "templates/index.html", merchant_currency=CURRENCY
+ )
##
@@ -179,11 +188,13 @@ def checkout():
amount = expect_parameter("donation_amount")
donation_receiver = expect_parameter("donation_receiver")
donation_donor = expect_parameter("donation_donor")
- return flask.render_template("templates/checkout.html",
- donation_amount=amount,
- donation_receiver=donation_receiver,
- donation_donor=donation_donor,
- merchant_currency=CURRENCY)
+ return flask.render_template(
+ "templates/checkout.html",
+ donation_amount=amount,
+ donation_receiver=donation_receiver,
+ donation_donor=donation_donor,
+ merchant_currency=CURRENCY
+ )
##
@@ -211,14 +222,16 @@ def donate():
payment_system = expect_parameter("payment_system")
if payment_system != "taler":
return flask.redirect(flask.url_for("provider_not_supported"))
- fulfillment_url = flask.url_for("fulfillment",
- receiver=donation_receiver,
- _external=True)
+ fulfillment_url = flask.url_for(
+ "fulfillment", receiver=donation_receiver, _external=True
+ )
order = dict(
amount=donation_amount,
- extra=dict(donor=donation_donor,
- receiver=donation_receiver,
- amount=donation_amount),
+ extra=dict(
+ donor=donation_donor,
+ receiver=donation_receiver,
+ amount=donation_amount
+ ),
fulfillment_url=fulfillment_url,
instance=donation_receiver,
summary="Donation to {}".format(donation_receiver),
@@ -226,9 +239,10 @@ def donate():
order_resp = backend_post("order", dict(order=order))
order_id = order_resp["order_id"]
return flask.redirect(
- flask.url_for("fulfillment",
- receiver=donation_receiver,
- order_id=order_id))
+ flask.url_for(
+ "fulfillment", receiver=donation_receiver, order_id=order_id
+ )
+ )
##
@@ -250,12 +264,14 @@ def fulfillment(receiver):
if pay_status.get("paid"):
extra = pay_status["contract_terms"]["extra"]
- return flask.render_template("templates/fulfillment.html",
- donation_receiver=extra["receiver"],
- donation_amount=extra["amount"],
- donation_donor=extra["donor"],
- order_id=order_id,
- currency=CURRENCY)
+ return flask.render_template(
+ "templates/fulfillment.html",
+ donation_receiver=extra["receiver"],
+ donation_amount=extra["amount"],
+ donation_donor=extra["donor"],
+ order_id=order_id,
+ currency=CURRENCY
+ )
# no pay_redirect but article not paid, this should never happen!
err_abort(500, message="Internal error, invariant failed", json=pay_status)
diff --git a/talerdonations/talerconfig.py b/talerdonations/talerconfig.py
index 7959ec2..1a33294 100644
--- a/talerdonations/talerconfig.py
+++ b/talerdonations/talerconfig.py
@@ -171,11 +171,13 @@ class Entry:
LOGGER.warning(
"Configuration is missing option '%s' in section '%s',\
falling back to '%s'", self.option,
- self.section, default)
+ self.section, default
+ )
else:
LOGGER.warning(
"Configuration ** is missing option '%s' in section '%s'",
- self.option.upper(), self.section.upper())
+ self.option.upper(), self.section.upper()
+ )
return default
return self.value
@@ -373,7 +375,8 @@ class TalerConfig:
# a error occurs).
def value_string(self, section, option, **kwargs) -> str:
return self.sections[section][option].value_string(
- kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
+ kwargs.get("default"), kwargs.get("required"), kwargs.get("warn")
+ )
##
# Get a value from the config that should be a filename.
@@ -388,7 +391,8 @@ class TalerConfig:
# a error occurs).
def value_filename(self, section, option, **kwargs) -> str:
return self.sections[section][option].value_filename(
- kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
+ kwargs.get("default"), kwargs.get("required"), kwargs.get("warn")
+ )
##
# Get a integer value from the config.
@@ -402,7 +406,8 @@ class TalerConfig:
# a error occurs).
def value_int(self, section, option, **kwargs) -> int:
return self.sections[section][option].value_int(
- kwargs.get("default"), kwargs.get("required"), kwargs.get("warn"))
+ kwargs.get("default"), kwargs.get("required"), kwargs.get("warn")
+ )
##
# Load default values from canonical locations.
@@ -477,48 +482,58 @@ class TalerConfig:
pair = line.split()
if 2 != len(pair):
LOGGER.error(
- "invalid inlined config filename given ('%s')"
- % line)
+ "invalid inlined config filename given ('%s')" %
+ line
+ )
continue
if pair[1].startswith("/"):
self.load_file(pair[1])
else:
self.load_file(
- os.path.join(os.path.dirname(filename),
- pair[1]))
+ os.path.join(
+ os.path.dirname(filename), pair[1]
+ )
+ )
continue
if line.startswith("["):
if not line.endswith("]"):
LOGGER.error(
- "invalid section header in line %s: %s",
- lineno, repr(line))
+ "invalid section header in line %s: %s", lineno,
+ repr(line)
+ )
section_name = line.strip("[]").strip().strip('"')
current_section = section_name
continue
if current_section is None:
LOGGER.error(
"option outside of section in line %s: %s", lineno,
- repr(line))
+ repr(line)
+ )
continue
pair = line.split("=", 1)
if len(pair) != 2:
- LOGGER.error("invalid option in line %s: %s", lineno,
- repr(line))
+ LOGGER.error(
+ "invalid option in line %s: %s", lineno, repr(line)
+ )
key = pair[0].strip()
value = pair[1].strip()
if value.startswith('"'):
value = value[1:]
if not value.endswith('"'):
- LOGGER.error("mismatched quotes in line %s: %s",
- lineno, repr(line))
+ LOGGER.error(
+ "mismatched quotes in line %s: %s", lineno,
+ repr(line)
+ )
else:
value = value[:-1]
- entry = Entry(self.sections,
- current_section,
- key,
- value=value,
- filename=filename,
- lineno=lineno)
+ entry = Entry(
+ self.sections,
+ current_section,
+ key,
+ value=value,
+ filename=filename,
+ lineno=lineno
+ )
sections[current_section][key] = entry
except FileNotFoundError:
# not logging here, as this interests the final user mostly.
@@ -561,26 +576,22 @@ if __name__ == "__main__":
import argparse
PARSER = argparse.ArgumentParser()
- PARSER.add_argument("--section",
- "-s",
- dest="section",
- default=None,
- metavar="SECTION")
- PARSER.add_argument("--option",
- "-o",
- dest="option",
- default=None,
- metavar="OPTION")
- PARSER.add_argument("--config",
- "-c",
- dest="config",
- default=None,
- metavar="FILE")
- PARSER.add_argument("--filename",
- "-f",
- dest="expand_filename",
- default=False,
- action='store_true')
+ PARSER.add_argument(
+ "--section", "-s", dest="section", default=None, metavar="SECTION"
+ )
+ PARSER.add_argument(
+ "--option", "-o", dest="option", default=None, metavar="OPTION"
+ )
+ PARSER.add_argument(
+ "--config", "-c", dest="config", default=None, metavar="FILE"
+ )
+ PARSER.add_argument(
+ "--filename",
+ "-f",
+ dest="expand_filename",
+ default=False,
+ action='store_true'
+ )
ARGS = PARSER.parse_args()
TC = TalerConfig.from_file(ARGS.config)
--
cgit v1.2.3
From e7c20a11fac21ab485b17feec81d0887be4cf5eb Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:09:02 +0200
Subject: url-based payments
---
talerdonations/donations/donations.py | 26 +++++++-
.../donations/templates/request_payment.html | 69 ++++++++++++++++++++++
2 files changed, 94 insertions(+), 1 deletion(-)
create mode 100644 talerdonations/donations/templates/request_payment.html
(limited to 'talerdonations')
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index 220fbb7..06c6617 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -245,6 +245,16 @@ def donate():
)
+##
+# This endpoint is used by the payment request page
+# to check if the payment has been completed via the QR code.
+@app.route("/check-status/")
+def check_status(order_id, session_id):
+ pay_params = dict( instance=INSTANCE, order_id=order_id)
+ pay_status = backend_get("check-payment", pay_params)
+ return flask.jsonify(paid=pay_status["paid"])
+
+
##
# Serve the fulfillment page.
#
@@ -260,7 +270,21 @@ def fulfillment(receiver):
pay_status = backend_get("check-payment", pay_params)
if pay_status.get("payment_redirect_url"):
- return flask.redirect(pay_status["payment_redirect_url"])
+ taler_pay_uri = pay_status["taler_pay_uri"]
+ qrcode_svg = get_qrcode_svg(taler_pay_uri)
+ check_status_url_enc = urllib.parse.quote(
+ flask.url_for("check_status", order_id=order_id)
+ )
+ content = flask.render_template(
+ "templates/request_payment.html",
+ article_name=article_name,
+ taler_pay_uri=taler_pay_uri,
+ qrcode_svg=qrcode_svg,
+ check_status_url_enc=check_status_url_enc
+ )
+ headers = {"Taler": taler_pay_uri}
+ resp = flask.Response(content, status=402, headers=headers)
+ return resp
if pay_status.get("paid"):
extra = pay_status["contract_terms"]["extra"]
diff --git a/talerdonations/donations/templates/request_payment.html b/talerdonations/donations/templates/request_payment.html
new file mode 100644
index 0000000..6e050d0
--- /dev/null
+++ b/talerdonations/donations/templates/request_payment.html
@@ -0,0 +1,69 @@
+{% extends "templates/base.html" %}
+
+
+{% block meta %}
+
+{% endblock meta %}
+
+
+{% block scripts %}
+
+{% endblock scripts %}
+
+
+{% block main %}
+
+Payment Required
+
+
+
+ Looks like your browser doesn't support GNU Taler payments. You can try
+ installing a wallet browser extension.
+
+
+
+
+
+
+ You can use this QR code to pay with your mobile wallet:
+
+
+ {{ qrcode_svg | safe }}
+
+
+ Click this link to open your system's Taler wallet if it exists.
+
+
+
+
+{% endblock main %}
--
cgit v1.2.3
From 31a05eaab426a6121a93945f1f2e08a686987560 Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:10:43 +0200
Subject: make pretty
---
talerdonations/donations/donations.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'talerdonations')
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index 06c6617..886705f 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -250,7 +250,7 @@ def donate():
# to check if the payment has been completed via the QR code.
@app.route("/check-status/")
def check_status(order_id, session_id):
- pay_params = dict( instance=INSTANCE, order_id=order_id)
+ pay_params = dict(instance=INSTANCE, order_id=order_id)
pay_status = backend_get("check-payment", pay_params)
return flask.jsonify(paid=pay_status["paid"])
--
cgit v1.2.3
From c75c8b0dc295a7278b5c6ba26cea9bbdb771789c Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:11:55 +0200
Subject: no more wallet detection
---
talerdonations/donations/templates/index.html | 17 +----------------
1 file changed, 1 insertion(+), 16 deletions(-)
(limited to 'talerdonations')
diff --git a/talerdonations/donations/templates/index.html b/talerdonations/donations/templates/index.html
index fc956e5..68a0cb2 100644
--- a/talerdonations/donations/templates/index.html
+++ b/talerdonations/donations/templates/index.html
@@ -7,22 +7,7 @@
You are paying with an imaginary currency ({{ merchant_currency }}).
-
-
Installing the Taler wallet
- First, you need to install the Taler wallet browser extension.
- Install the wallet
-
(done)
-
- Wallets for other browsers will be provided in the near future.
-
-
-
+
Please select a project, the amount (*) of {{ merchant_currency }} you
wish to donate, and enter the name that will appear on your receipt:
--
cgit v1.2.3
From 06bf80ca7326e9e1c72b27cb4734f9eefc20f68e Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:13:16 +0200
Subject: no more redirect URL
---
talerdonations/donations/donations.py | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
(limited to 'talerdonations')
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index 886705f..d4dd36d 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -269,7 +269,18 @@ def fulfillment(receiver):
pay_params = dict(instance=receiver, order_id=order_id)
pay_status = backend_get("check-payment", pay_params)
- if pay_status.get("payment_redirect_url"):
+
+ if pay_status.get("paid"):
+ extra = pay_status["contract_terms"]["extra"]
+ return flask.render_template(
+ "templates/fulfillment.html",
+ donation_receiver=extra["receiver"],
+ donation_amount=extra["amount"],
+ donation_donor=extra["donor"],
+ order_id=order_id,
+ currency=CURRENCY
+ )
+ else:
taler_pay_uri = pay_status["taler_pay_uri"]
qrcode_svg = get_qrcode_svg(taler_pay_uri)
check_status_url_enc = urllib.parse.quote(
@@ -286,16 +297,5 @@ def fulfillment(receiver):
resp = flask.Response(content, status=402, headers=headers)
return resp
- if pay_status.get("paid"):
- extra = pay_status["contract_terms"]["extra"]
- return flask.render_template(
- "templates/fulfillment.html",
- donation_receiver=extra["receiver"],
- donation_amount=extra["amount"],
- donation_donor=extra["donor"],
- order_id=order_id,
- currency=CURRENCY
- )
-
# no pay_redirect but article not paid, this should never happen!
err_abort(500, message="Internal error, invariant failed", json=pay_status)
--
cgit v1.2.3
From 8ef2843d05d57c07115dba494cc913efc3f19014 Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:15:09 +0200
Subject: qrcode
---
setup.py | 2 +-
talerdonations/donations/donations.py | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
(limited to 'talerdonations')
diff --git a/setup.py b/setup.py
index 906567c..042635f 100755
--- a/setup.py
+++ b/setup.py
@@ -9,7 +9,7 @@ setup(
author_email=['stanisci.m@gmail.com', 'dold@taler.net'],
license='GPL',
packages=find_packages(),
- install_requires=["Flask>=0.10", "requests", "uwsgi", "jsmin"],
+ install_requires=["Flask>=0.10", "requests", "uwsgi", "jsmin", "qrcode", "lxml"],
tests_require=["mock", "nose"],
test_suite="nose.collector",
package_data={
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index d4dd36d..c00844b 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -26,6 +26,9 @@ import random
import requests
import flask
import traceback
+import qrcode
+import qrcode.image.svg
+import lxml.etree
from ..talerconfig import TalerConfig
LOGGER = logging.getLogger(__name__)
@@ -255,6 +258,12 @@ def check_status(order_id, session_id):
return flask.jsonify(paid=pay_status["paid"])
+def get_qrcode_svg(data):
+ factory = qrcode.image.svg.SvgImage
+ img = qrcode.make(data, image_factory=factory)
+ return lxml.etree.tostring(img.get_image()).decode("utf-8")
+
+
##
# Serve the fulfillment page.
#
--
cgit v1.2.3
From cdd70817d77265a897e4efb1fed22e66e2c99ce9 Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:15:58 +0200
Subject: urllib
---
talerdonations/donations/donations.py | 1 +
1 file changed, 1 insertion(+)
(limited to 'talerdonations')
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index c00844b..21eb76a 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -26,6 +26,7 @@ import random
import requests
import flask
import traceback
+import urllib
import qrcode
import qrcode.image.svg
import lxml.etree
--
cgit v1.2.3
From b38365fa582a52592d00f36e8dee2ee9bfca9ee6 Mon Sep 17 00:00:00 2001
From: Florian Dold
Date: Thu, 29 Aug 2019 19:16:45 +0200
Subject: I want static typing
---
talerdonations/donations/donations.py | 1 -
1 file changed, 1 deletion(-)
(limited to 'talerdonations')
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index 21eb76a..d85c9a9 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -298,7 +298,6 @@ def fulfillment(receiver):
)
content = flask.render_template(
"templates/request_payment.html",
- article_name=article_name,
taler_pay_uri=taler_pay_uri,
qrcode_svg=qrcode_svg,
check_status_url_enc=check_status_url_enc
--
cgit v1.2.3