diff --git a/README.md b/README.md index eccf0d7..d2107b7 100644 --- a/README.md +++ b/README.md @@ -22,25 +22,37 @@ source venv/bin/activate pip3 install requirements.txt ``` -Create a .env file with : +Create a settings.py file with : ``` -PORT=8000 -DEBUG=True -PROJECT_NAME=YunoHost -DOMAIN=http://localhost:8000 -STATIC_DIR=assets -SECRET_CSRF_KEY=TO_CHANGE +ENV = 'development' +PORT = 8000 +DOMAIN = 'http://localhost:8000' +SECRET_KEY = '712AZPOC87HXD5SQSb12rd' +SECRET_CSRF_KEY = '712AZPOC87HXD5SQSb12' +LANGUAGES = ['en', 'fr'] +BABEL_TRANSLATION_DIRECTORIES = 'locales' + +# Customization +CUSTOM = {} +CUSTOM['name'] = 'YunoHost' +CUSTOM['contact_url'] = 'mailto:donate-6521@yunohost.org' +CUSTOM['logo'] = 'https://yunohost.org/user/images/logo.png' +CUSTOM['favicon'] = 'https://yunohost.org/user/themes/yunohost-docs/images/favicon.png' +CUSTOM['currencies'] = [ + ('EUR', '€'), + ('USD', '$') +] # Stripe keys -STRIPE_PUBLISHABLE_KEY=pk_test_gOgGjacs9YfvDJY03BRZ576O -STRIPE_SECRET_KEY=TO_REPLACE_BY_THE_GOOD_VALUE +CUSTOM['stripe_publishable_key'] = 'pk_test_gOgGjacs9YfvDJY03BRZ576O' +STRIPE_SECRET_KEY = 'sk_test_' # Stripe subscription data -ONE_TIME_EUR_DONATION=price_1IKuPVE7vOmTpJBiYMq7ztLH -RECURING_EUR_DONATION=price_1IKumjE7vOmTpJBikyqS2NqD - -ONE_TIME_USD_DONATION=price_1IKuQfE7vOmTpJBi0A3nRGCJ -RECURING_USD_DONATION=price_1IKumAE7vOmTpJBiO4CEfa3Q +DONATION={'one_time':{}, 'recuring': {}} +DONATION['one_time']['EUR'] = 'price_1IKuPVE7vOmTpJBiYMq7ztLH' +DONATION['one_time']['USD'] = 'price_1IKuQfE7vOmTpJBi0A3nRGCJ' +DONATION['recuring']['EUR'] = 'price_1IKumjE7vOmTpJBikyqS2NqD' +DONATION['recuring']['USD'] = 'price_1IKumAE7vOmTpJBiO4CEfa3Q' ``` ``` diff --git a/assets/canceled.html b/assets/canceled.html index 4a6ebfb..4befef3 100644 --- a/assets/canceled.html +++ b/assets/canceled.html @@ -4,9 +4,9 @@ - Donate to YunoHost + {{ _('Donate to %(name)s', name=name) }} - + @@ -14,10 +14,10 @@
- -

Your payment was canceled

+ +

{{ _('Your payment was canceled') }}

- +
diff --git a/assets/favicon.ico b/assets/favicon.ico deleted file mode 100644 index 49ffcee..0000000 Binary files a/assets/favicon.ico and /dev/null differ diff --git a/assets/index.html b/assets/index.html index ec9b9af..655e4b6 100644 --- a/assets/index.html +++ b/assets/index.html @@ -4,9 +4,9 @@ - Donate to YunoHost + {{ _('Donate to %(name)s', name=name) }} - + @@ -16,22 +16,25 @@
- -

I want to give to YunoHost

+ +

{{ _('I want to give to %(name)s', name=name) }}

+ + - + {% for iso, symbol in currencies %} +
- - + +

{{ _('If you want to stop a monthly donation contact us') }}

diff --git a/assets/index.js b/assets/index.js index c97a962..710bef1 100644 --- a/assets/index.js +++ b/assets/index.js @@ -1,24 +1,12 @@ -// Fetch your Stripe publishable key to initialize Stripe.js -// In practice, you might just hard code the publishable API -// key here. -fetch('/config') - .then(function (result) { - return result.json(); - }) - .then(function (json) { - window.config = json; - window.stripe = Stripe(config.publicKey); - }); +window.stripe = Stripe(document.getElementById('public_key').value); // When the form is submitted... var submitBtn = document.querySelector('#submit'); submitBtn.addEventListener('click', function (evt) { - var inputEl = document.getElementById('quantity'); - var quantity = parseInt(inputEl.value); - inputEl = document.getElementById('currency'); - var currency = inputEl.value; - inputEl = document.getElementById('frequency'); - var frequency = inputEl.value; + var quantity = parseInt(document.getElementById('quantity').value); + var currency = document.getElementById('currency').value; + var frequency = document.getElementById('frequency').value; + var csrf = document.getElementById('csrf').value; // Create the checkout session. fetch('/create-checkout-session', { @@ -27,7 +15,7 @@ submitBtn.addEventListener('click', function (evt) { 'Content-Type': 'application/json', }, body: JSON.stringify({ - user_csrf: window.config.csrf, + user_csrf: csrf, quantity: quantity, currency: currency, frequency: frequency diff --git a/assets/logo.png b/assets/logo.png deleted file mode 100644 index ff113c9..0000000 Binary files a/assets/logo.png and /dev/null differ diff --git a/assets/success.html b/assets/success.html index d865b26..d698d9c 100644 --- a/assets/success.html +++ b/assets/success.html @@ -4,9 +4,9 @@ - Donate to YunoHost + {{ _('Donate to %(name)s', name=name) }} - + @@ -14,10 +14,10 @@
- -

Thanks for your donation 🙂

+ +

{{ _('Thanks for your donation 🙂') }}

- +
diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..0d3d70b --- /dev/null +++ b/babel.cfg @@ -0,0 +1,3 @@ +[python: server.py] +[jinja2: assets/**.html] +extensions=jinja2.ext.autoescape,jinja2.ext.with_ diff --git a/locales/en/LC_MESSAGES/messages.po b/locales/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000..fa9e4e0 --- /dev/null +++ b/locales/en/LC_MESSAGES/messages.po @@ -0,0 +1,54 @@ +# English translations for PROJECT. +# Copyright (C) 2021 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2021. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2021-02-18 23:32+0100\n" +"PO-Revision-Date: 2021-02-18 23:33+0100\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.9.0\n" + +#: assets/canceled.html:7 assets/index.html:7 assets/success.html:7 +#, python-format +msgid "Donate to %(name)s" +msgstr "" + +#: assets/canceled.html:18 +msgid "Your payment was canceled" +msgstr "" + +#: assets/canceled.html:20 assets/success.html:20 +msgid "Go back to the donate form" +msgstr "" + +#: assets/index.html:20 +#, python-format +msgid "I want to give to %(name)s" +msgstr "" + +#: assets/index.html:31 +msgid "/ month" +msgstr "" + +#: assets/index.html:32 +msgid "one time" +msgstr "" + +#: assets/index.html:36 +msgid "Donate" +msgstr "" + +#: assets/success.html:18 +msgid "Thanks for your donation 🙂" +msgstr "" + diff --git a/locales/fr/LC_MESSAGES/messages.po b/locales/fr/LC_MESSAGES/messages.po new file mode 100644 index 0000000..b316e6c --- /dev/null +++ b/locales/fr/LC_MESSAGES/messages.po @@ -0,0 +1,54 @@ +# French translations for PROJECT. +# Copyright (C) 2021 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2021. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2021-02-18 23:32+0100\n" +"PO-Revision-Date: 2021-02-18 23:33+0100\n" +"Last-Translator: FULL NAME \n" +"Language: fr\n" +"Language-Team: fr \n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.9.0\n" + +#: assets/canceled.html:7 assets/index.html:7 assets/success.html:7 +#, python-format +msgid "Donate to %(name)s" +msgstr "Donner à %(name)s" + +#: assets/canceled.html:18 +msgid "Your payment was canceled" +msgstr "Votre paiement a été annulé" + +#: assets/canceled.html:20 assets/success.html:20 +msgid "Go back to the donate form" +msgstr "Retourner au formulaire de don" + +#: assets/index.html:20 +#, python-format +msgid "I want to give to %(name)s" +msgstr "Je veux donner à %(name)s" + +#: assets/index.html:31 +msgid "/ month" +msgstr "/ mois" + +#: assets/index.html:32 +msgid "one time" +msgstr "une fois" + +#: assets/index.html:36 +msgid "Donate" +msgstr "Donner" + +#: assets/success.html:18 +msgid "Thanks for your donation 🙂" +msgstr "Merci pour votre don 🙂" + diff --git a/requirements.txt b/requirements.txt index 00bc321..1956d56 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ stripe==2.47.0 toml==0.9.6 urllib3==1.25.3 flask-simple-csrf +flask-babel diff --git a/server.py b/server.py index 85497c2..eeb8920 100644 --- a/server.py +++ b/server.py @@ -13,25 +13,21 @@ import random import string from flask import Flask, render_template, jsonify, request, send_from_directory, session +from flask_babel import Babel, _ from flask_simple_csrf import CSRF -from dotenv import load_dotenv, find_dotenv -# Setup Stripe python client library. -load_dotenv(find_dotenv()) - -stripe.api_key = os.getenv('STRIPE_SECRET_KEY') -stripe.api_version = os.getenv('STRIPE_API_VERSION') - static_dir = str(os.path.abspath(os.path.join( - __file__, "..", os.getenv("STATIC_DIR")))) + __file__, "..", 'assets'))) app = Flask(__name__, static_folder=static_dir, static_url_path="", template_folder=static_dir) -app.secret_key = os.getenv('SECRET_KEY') +app.config.from_pyfile('settings.py') +stripe.api_key = app.config['STRIPE_SECRET_KEY'] CSRF = CSRF(config={ - 'SECRET_CSRF_KEY':os.getenv('SECRET_CSRF_KEY') + 'SECRET_CSRF_KEY': app.config['SECRET_CSRF_KEY'] }) app = CSRF.init_app(app) +babel = Babel(app) @app.before_request def before_request(): @@ -39,43 +35,52 @@ def before_request(): session['USER_CSRF'] = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(64)) session['CSRF_TOKEN'] = CSRF.create(session['USER_CSRF']) +@babel.localeselector +def get_locale(): + return 'fr' #request.accept_languages.best_match(app.config['LANGUAGES']) + @app.route('/', methods=['GET']) def get_index(): - return render_template('index.html') + return render_template('index.html', **app.config['CUSTOM'], + csrf=session['USER_CSRF']) -@app.route('/config', methods=['GET']) -def get_publishable_key(): - return jsonify({ - 'publicKey': os.getenv('STRIPE_PUBLISHABLE_KEY'), - 'name': os.getenv('PROJECT_NAME'), - 'csrf': session['USER_CSRF'], - }) +@app.route('/success', methods=['GET']) +def get_success(): + return render_template('success.html', **app.config['CUSTOM']) + + +@app.route('/canceled', methods=['GET']) +def get_canceled(): + return render_template('canceled.html', **app.config['CUSTOM']) + @app.route('/create-checkout-session', methods=['POST']) def create_checkout_session(): data = json.loads(request.data) - domain_url = os.getenv('DOMAIN') + domain_url = app.config['DOMAIN'] try: + donation = app.config['DONATION'] + currencies = [iso for iso, symbol in app.config['CUSTOM']['currencies']] if CSRF.verify(data['user_csrf'], session['CSRF_TOKEN']) is False or \ - data['frequency'] not in ['RECURING', 'ONE_TIME'] or \ - data['currency'] not in ['EUR', 'USD'] or \ + data['frequency'] not in ['recuring', 'one_time'] or \ + data['currency'] not in currencies or \ int(data['quantity']) <= 0: return jsonify(error="Bad value"), 400 # Create new Checkout Session for the order - price = f"{data['frequency']}_{data['currency']}_DONATION" - mode = "payment" if data['frequency'] == 'ONE_TIME' else "subscription" + price = donation[data['frequency']][data['currency']] + mode = "payment" if data['frequency'] == 'one_time' else "subscription" checkout_session = stripe.checkout.Session.create( success_url=domain_url + - "/success.html?session_id={CHECKOUT_SESSION_ID}", - cancel_url=domain_url + "/canceled.html", + "/success?session_id={CHECKOUT_SESSION_ID}", + cancel_url=domain_url + "/canceled", payment_method_types= ["card"], mode=mode, line_items=[ { - "price": os.getenv(price), + "price": price, "quantity": data['quantity'] } ] @@ -87,4 +92,4 @@ def create_checkout_session(): if __name__ == '__main__': - app.run(port=os.getenv('PORT'), debug=os.getenv('DEBUG')) + app.run(port=app.config['PORT'], debug=app.debug)