summaryrefslogtreecommitdiff
path: root/talerdonations
diff options
context:
space:
mode:
Diffstat (limited to 'talerdonations')
-rw-r--r--talerdonations/donations/donations.py113
-rw-r--r--talerdonations/donations/templates/index.html17
-rw-r--r--talerdonations/donations/templates/request_payment.html69
-rw-r--r--talerdonations/tests.py1
4 files changed, 159 insertions, 41 deletions
diff --git a/talerdonations/donations/donations.py b/talerdonations/donations/donations.py
index a7b41c2..9fd28c1 100644
--- a/talerdonations/donations/donations.py
+++ b/talerdonations/donations/donations.py
@@ -26,6 +26,10 @@ import random
import requests
import flask
import traceback
+import urllib
+import qrcode
+import qrcode.image.svg
+import lxml.etree
from taler.util.talerconfig import TalerConfig
LOGGER = logging.getLogger(__name__)
@@ -53,6 +57,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 +81,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 +91,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
@@ -100,21 +111,29 @@ 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
-
##
# Inspect GET arguments in the look for a parameter.
#
@@ -126,6 +145,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.
#
@@ -134,9 +154,12 @@ 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()
+ )
+
##
# Serve the main index page.
@@ -144,7 +167,10 @@ 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
+ )
+
##
# Serve the "/javascript" page.
@@ -155,7 +181,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,
@@ -172,7 +197,8 @@ def checkout():
donation_amount=amount,
donation_receiver=donation_receiver,
donation_donor=donation_donor,
- merchant_currency=CURRENCY)
+ merchant_currency=CURRENCY
+ )
##
@@ -182,8 +208,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 +226,43 @@ 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
+ )
+ )
+
+
+##
+# 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/<order_id>")
+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"])
+
+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")
##
@@ -226,12 +276,9 @@ def donate():
@app.route("/donation/<receiver>")
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"):
- return flask.redirect(pay_status["payment_redirect_url"])
if pay_status.get("paid"):
extra = pay_status["contract_terms"]["extra"]
@@ -241,7 +288,23 @@ def fulfillment(receiver):
donation_amount=extra["amount"],
donation_donor=extra["donor"],
order_id=order_id,
- currency=CURRENCY)
+ 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(
+ flask.url_for("check_status", order_id=order_id)
+ )
+ content = flask.render_template(
+ "templates/request_payment.html",
+ 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
# 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/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 }}).
</p>
-<div class="taler-installed-hide">
- <h2>Installing the Taler wallet</h2>
- First, you need to install the Taler wallet browser extension.
- Install the wallet
- <span id="install-done" style="visibility: hidden">(done)</span>
- <ul>
- <li>from the app store for <a href="https://chrome.google.com/webstore/detail/gnu-taler-wallet/millncjiddlpgdmkklmhfadpacifaonc">Google
- Chrome and Chromium</a>
- </li>
- <li>By visiting our <a href="https://taler.net/wallet">installation page</a> for other platforms.
- </li>
- </ul>
- Wallets for other browsers will be provided in the near future.
-</div>
-
-<div class="taler-installed-show">
+<div>
<p>Please select a project, the amount (*) of {{ merchant_currency }} you
wish to donate, and enter the name that will appear on your receipt:</p>
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 %}
+<noscript>
+ <meta http-equiv="refresh" content="1">
+</noscript>
+{% endblock meta %}
+
+
+{% block scripts %}
+<script>
+ let checkUrl = decodeURIComponent("{{ check_status_url_enc }}");
+ let delayMs = 500;
+ function check() {
+ let req = new XMLHttpRequest();
+ req.onreadystatechange = function () {
+ if (req.readyState === XMLHttpRequest.DONE) {
+ if (req.status === 200) {
+ try {
+ let resp = JSON.parse(req.responseText);
+ if (resp.paid) {
+ document.location.reload(true);
+ }
+ } catch (e) {
+ console.error("could not parse response:", e);
+ }
+ }
+ setTimeout(check, delayMs);
+ }
+ };
+ req.onerror = function () {
+ setTimeout(check, delayMs);
+ }
+ req.open("GET", checkUrl);
+ req.send();
+ }
+
+ setTimeout(check, delayMs);
+</script>
+{% endblock scripts %}
+
+
+{% block main %}
+
+<h1>Payment Required</h1>
+
+<div class="taler-installed-hide">
+ <p>
+ Looks like your browser doesn't support GNU Taler payments. You can try
+ installing a <a href="https://taler.net/en/wallet.html">wallet browser extension</a>.
+ </p>
+</div>
+
+<div>
+
+ <p>
+ You can use this QR code to pay with your mobile wallet:
+ </p>
+
+ {{ qrcode_svg | safe }}
+
+ <p>
+ Click <a href="{{ taler_pay_uri }}">this link</a> to open your system's Taler wallet if it exists.
+ </p>
+
+</div>
+
+{% endblock main %}
diff --git a/talerdonations/tests.py b/talerdonations/tests.py
index cb6553b..ef392ee 100644
--- a/talerdonations/tests.py
+++ b/talerdonations/tests.py
@@ -27,6 +27,7 @@ from taler.util.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):