# This file is part of GNU TALER. # Copyright (C) 2014-2016 INRIA # # TALER is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free Software # Foundation; either version 2.1, or (at your option) any later version. # # TALER is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License along with # GNU TALER; see the file COPYING. If not, see # # @author Florian Dold # @author Marcello Stanisci from urllib.parse import urljoin, parse_qsl import logging import os import base64 import random from datetime import datetime import requests import flask from ..talerconfig import TalerConfig from ..helpers import (make_url, \ expect_parameter, amount_from_float, \ amount_to_float, join_urlparts, get_query_string, \ MissingParameterException, backend_error) LOGGER = logging.getLogger(__name__) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) app = flask.Flask(__name__, template_folder=BASE_DIR) app.debug = True app.secret_key = base64.b64encode(os.urandom(64)).decode('utf-8') TC = TalerConfig.from_env() BACKEND_URL = TC["frontends"]["backend"].value_string(required=True) CURRENCY = TC["taler"]["currency"].value_string(required=True) MAX_FEE = dict(value=3, fraction=0, currency=CURRENCY) app.config.from_object(__name__) @app.context_processor def utility_processor(): def url(my_url): return join_urlparts(flask.request.script_root, my_url) def env(name, default=None): return os.environ.get(name, default) return dict(url=url, env=env) @app.route("/") def index(): return flask.render_template("templates/index.html", merchant_currency=CURRENCY) @app.route("/javascript") def javascript_licensing(): return flask.render_template("templates/javascript.html") @app.route("/checkout", methods=["GET"]) def checkout(): amount_str = expect_parameter("donation_amount") donation_receiver = expect_parameter("donation_receiver") try: float(amount_str) except ValueError: LOGGER.warning("Invalid amount ('%s')", amount_str) return flask.make_response( flask.jsonify(error="invalid amount"), 400) display_alert = flask.request.args.get("display_alert", None) return flask.render_template( "templates/checkout.html", donation_amount=amount_str, donation_receiver=donation_receiver, merchant_currency=CURRENCY, display_alert=display_alert) @app.route("/generate-contract", methods=["GET"]) def generate_contract(): try: donation_receiver = expect_parameter("donation_receiver") donation_amount = expect_parameter("donation_amount") except MissingParameterException as exc: return flask.jsonify( dict(error="Missing parameter '%s'" % exc)), 400 amount = amount_from_float(float(donation_amount)) order_id = "donation-%s-%X-%s" % \ (donation_receiver, random.randint(0, 0xFFFFFFFF), datetime.today().strftime('%H_%M_%S')) order = dict( summary="Donation!", nonce=flask.request.args.get("nonce"), amount=amount, max_fee=dict(value=1, fraction=0, currency=CURRENCY), order_id=order_id, products=[ dict( description="Donation to %s" % (donation_receiver,), quantity=1, product_id=0, price=amount, ), ], fulfillment_url=make_url("/fulfillment", ("order_id", order_id)), pay_url=make_url("/pay"), merchant=dict( instance=donation_receiver, address="nowhere", name="Kudos Inc.", jurisdiction="none", ), ) resp = requests.post(urljoin(BACKEND_URL, 'proposal'), json=dict(order=order)) if resp.status_code != 200: # It is important to use 'backend_error()', as it handles # the case where the backend gives NO JSON as response. # For example, if it dies, or nginx hijacks somehow the # response. return backend_error(resp) return flask.jsonify(resp.json()), resp.status_code @app.route("/donate") def donate(): donation_receiver = expect_parameter("donation_receiver") donation_amount = expect_parameter("donation_amount") payment_system = expect_parameter("payment_system") if payment_system != "taler": return flask.redirect(make_url("checkout", ("donation_receiver", donation_receiver), ("donation_amount", donation_amount), ("display_alert", True))) response = flask.make_response(flask.render_template('templates/fallback.html'), 402) response.headers["X-Taler-Contract-Url"] = \ make_url("/generate-contract", ("donation_receiver", donation_receiver), ("donation_amount", donation_amount)) return response @app.route("/fulfillment") def fulfillment(): order_id = expect_parameter("order_id") payed_order_ids = flask.session.get("payed_order_ids", []) print("order_id:", repr(order_id)) print("session:", repr(flask.session)) if order_id in payed_order_ids: data = payed_order_ids[order_id] return flask.render_template( "templates/fulfillment.html", donation_receiver=data["donation_receiver"], donation_amount=data["donation_amount"], order_id=order_id, currency=CURRENCY) response = flask.make_response(flask.render_template("templates/fallback.html"), 402) response.headers["X-Taler-Contract-Query"] = "fulfillment_url" response.headers["X-Taler-Offer-Url"] = make_url("/") return response @app.route("/pay", methods=["POST"]) def pay(): deposit_permission = flask.request.get_json() if deposit_permission is None: return flask.jsonify(error="no json in body"), 400 resp = requests.post(urljoin(BACKEND_URL, "pay"), json=deposit_permission) if resp.status_code != 200: return backend_error(resp) proposal_data = resp.json()["contract_terms"] order_id = proposal_data["order_id"] payed_order_ids = flask.session["payed_order_ids"] \ = flask.session.get("payed_order_ids", {}) payed_order_ids[order_id] = dict( donation_receiver=proposal_data["merchant"]["instance"], donation_amount=amount_to_float(proposal_data["amount"]) ) print("received payment for", order_id) return flask.jsonify(resp.json()), resp.status_code @app.route("/backoffice") def track(): response = flask.make_response(flask.render_template("templates/backoffice.html")) return response @app.route("/history") def history(): qs = get_query_string().decode("utf-8") url = urljoin(BACKEND_URL, "history") resp = requests.get(url, params=dict(parse_qsl(qs))) if resp.status_code != 200: return backend_error(resp) return flask.jsonify(resp.json()), resp.status_code @app.route("/track/order") def track_order(): instance = expect_parameter("instance") order_id = expect_parameter("order_id") url = urljoin(BACKEND_URL, "track/transaction") resp = requests.get(url, params=dict(order_id=order_id, instance=instance)) if resp.status_code != 200: return backend_error(resp) return flask.jsonify(resp.json()), resp.status_code