mirror of
https://github.com/YunoHost/pepettes.git
synced 2024-09-03 20:06:20 +02:00
i18n + custom settings
This commit is contained in:
parent
69786aa3de
commit
0e15d68123
12 changed files with 200 additions and 80 deletions
38
README.md
38
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 :
|
||||
```
|
||||
ENV = 'development'
|
||||
PORT = 8000
|
||||
DEBUG=True
|
||||
PROJECT_NAME=YunoHost
|
||||
DOMAIN=http://localhost:8000
|
||||
STATIC_DIR=assets
|
||||
SECRET_CSRF_KEY=TO_CHANGE
|
||||
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'
|
||||
```
|
||||
|
||||
```
|
||||
|
|
|
@ -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 |
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
|
@ -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
3
babel.cfg
Normal file
|
@ -0,0 +1,3 @@
|
|||
[python: server.py]
|
||||
[jinja2: assets/**.html]
|
||||
extensions=jinja2.ext.autoescape,jinja2.ext.with_
|
54
locales/en/LC_MESSAGES/messages.po
Normal file
54
locales/en/LC_MESSAGES/messages.po
Normal 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 ""
|
||||
|
54
locales/fr/LC_MESSAGES/messages.po
Normal file
54
locales/fr/LC_MESSAGES/messages.po
Normal 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 🙂"
|
||||
|
|
@ -12,3 +12,4 @@ stripe==2.47.0
|
|||
toml==0.9.6
|
||||
urllib3==1.25.3
|
||||
flask-simple-csrf
|
||||
flask-babel
|
||||
|
|
59
server.py
59
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)
|
||||
|
|
Loading…
Reference in a new issue