i18n + custom settings

This commit is contained in:
ljf 2021-02-19 00:14:04 +01:00
parent 69786aa3de
commit 0e15d68123
12 changed files with 200 additions and 80 deletions

View file

@ -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'
```
```

View file

@ -4,9 +4,9 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Donate to YunoHost</title>
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<link rel="icon" href="{{ favicon }}" type="image/x-icon" />
<link href="./css/bootstrap-5.0.0-beta2.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
<link href="./css/global.css" rel="stylesheet" />
</head>
@ -14,10 +14,10 @@
<body class="text-center">
<main class="form-donate">
<div>
<img src="logo.png" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">Your payment was canceled</h1>
<img src="{{ logo }}" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">{{ _('Your payment was canceled') }}</h1>
<button onclick="window.location.href = '/';" class="w-100 btn btn-lg btn-primary">Go back to the donate form</button>
<button onclick="window.location.href = '/';" class="w-100 btn btn-lg btn-primary">{{ _('Go back to the donate form') }}</button>
</div>
</main>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -4,9 +4,9 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Donate to YunoHost</title>
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<link rel="icon" href="{{ favicon }}" type="image/x-icon" />
<link href="./css/bootstrap-5.0.0-beta2.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
<link href="./css/global.css" rel="stylesheet" />
<script src="https://js.stripe.com/v3/"></script>
@ -16,22 +16,25 @@
<body class="text-center">
<main class="form-donate">
<div>
<img src="logo.png" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">I want to give to YunoHost</h1>
<img src="{{ logo }}" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">{{ _('I want to give to %(name)s', name=name) }}</h1>
<div class="input-group mb-3">
<input type="hidden" id="csrf" value="{{ csrf }}" />
<input type="hidden" id="public_key" value="{{ stripe_publishable_key }}" />
<input type="number" min="0" max="9999" class="form-control" id="quantity" value="10" aria-label="Amount">
<select class="form-control" id="currency">
<option value="EUR">
<option value="USD">$ (USD)
<select class="form-control" id="currency" style="width:40px">
{% for iso, symbol in currencies %}
<option value="{{ iso }}">{{ symbol }}
{% endfor %}
</select>
<select class="form-control" id="frequency">
<option value="RECURING"> / month
<option value="ONE_TIME"> one time
<option value="recuring"> {{ _('/ month') }}
<option value="one_time"> {{ _('one time') }}
</select>
</div>
<button id="submit" class="w-100 btn btn-lg btn-primary">Donate</button>
<button id="submit" class="w-100 btn btn-lg btn-primary">{{ _('Donate') }}</button>
<p><a href="{{ contact_url }}">{{ _('If you want to stop a monthly donation contact us') }}</a></p>
<div id="error-message"></div>
</div>
</main>

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -4,9 +4,9 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Donate to YunoHost</title>
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<link rel="icon" href="{{ favicon }}" type="image/x-icon" />
<link href="./css/bootstrap-5.0.0-beta2.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
<link href="./css/global.css" rel="stylesheet" />
</head>
@ -14,10 +14,10 @@
<body class="text-center">
<main class="form-donate">
<div>
<img src="logo.png" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">Thanks for your donation 🙂</h1>
<img src="{{ logo }}" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">{{ _('Thanks for your donation 🙂') }}</h1>
<button onclick="window.location.href = '/';" class="w-100 btn btn-lg btn-primary">Go back to the donate form</button>
<button onclick="window.location.href = '/';" class="w-100 btn btn-lg btn-primary">{{ _('Go back to the donate form') }}</button>
</div>
</main>

3
babel.cfg Normal file
View file

@ -0,0 +1,3 @@
[python: server.py]
[jinja2: assets/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View file

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <LL@li.org>\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 ""

View file

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\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 🙂"

View file

@ -12,3 +12,4 @@ stripe==2.47.0
toml==0.9.6
urllib3==1.25.3
flask-simple-csrf
flask-babel

View file

@ -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)