Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

14 changed files with 159 additions and 309 deletions

View file

@ -1,35 +0,0 @@
name: Check / auto apply Black
on:
push:
branches:
- main
jobs:
black:
name: Check / auto apply black
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check files using the black formatter
uses: psf/black@stable
id: black
with:
options: "."
continue-on-error: true
- shell: pwsh
id: check_files_changed
run: |
# Diff HEAD with the previous commit
$diff = git diff
$HasDiff = $diff.Length -gt 0
Write-Host "::set-output name=files_changed::$HasDiff"
- name: Create Pull Request
if: steps.check_files_changed.outputs.files_changed == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: "Format Python code with Black"
commit-message: ":art: Format Python code with Black"
body: |
This pull request uses the [psf/black](https://github.com/psf/black) formatter.
base: ${{ github.head_ref }} # Creates pull request onto pull request or commit branch
branch: actions/black

3
.gitignore vendored
View file

@ -127,6 +127,3 @@ dmypy.json
# Pyre type checker
.pyre/
# Config file
settings.py

View file

@ -8,23 +8,22 @@ The demo is running in test mode -- use `4242424242424242` as a test card number
Use the `4000002500003155` test card number to trigger a 3D Secure challenge flow.
Read more about testing on Stripe at <https://stripe.com/docs/testing>.
Read more about testing on Stripe at https://stripe.com/docs/testing.
## How to run locally
Follow the steps below to run locally.
```bash
git clone https://github.com/YunoHost/pepettes
cd pepettes
```
git clone https://github.com/yunohost/donate
cd donate
python3 -m venv venv
source venv/bin/activate
python3 -m pip install -r requirements.txt
pip3 install requirements.txt
```
Create a settings.py file with :
```python
```
ENV = 'development'
PORT = 8000
DOMAIN = 'http://localhost:8000'
@ -56,15 +55,7 @@ DONATION['recuring']['EUR'] = 'price_1IKumjE7vOmTpJBikyqS2NqD'
DONATION['recuring']['USD'] = 'price_1IKumAE7vOmTpJBiO4CEfa3Q'
```
```bash
```
export FLASK_APP=server.py
python3 -m flask run --port=8000
```
```bash
# extract new strings or update existing strings
pybabel extract -F babel.cfg -o messages.pot *.py assets/*.html
# then update the locale files, please translate into french any new string
pybabel update -i messages.pot -d locales
```

View file

@ -1,32 +1,25 @@
<!DOCTYPE html>
<html lang="{{ lang }}">
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<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>
<link rel="icon" href="{{ favicon }}" type="image/x-icon" />
<link href="./css/bootstrap-5.3.3.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous" />
<link href="./css/global.css" rel="stylesheet" />
<script src="./darkmode.js" defer></script>
</head>
<body class="text-center">
<main class="form-donate">
<div>
<img src="{{ logo }}" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">{{ _('Your payment was canceled') }}</h1>
<body class="text-center">
<main class="form-donate">
<div>
<img src="{{ logo }}" class="mb-4" alt="{{ _('%(name)s Logo', name=name) }}" />
<button onclick="window.location.href = '/';" class="w-100 btn btn-lg btn-primary">{{ _('Go back to the donate form') }}</button>
<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>
</div>
</main>
</body>
</html>
</div>
</main>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -8,16 +8,7 @@ body {
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
}
@media (prefers-color-scheme: dark) {
img {
filter: invert(100%);
}
}
img {
max-width: 13em
background-color: #f5f5f5;
}
.form-donate {
@ -26,11 +17,9 @@ img {
padding: 15px;
margin: auto;
}
.form-donate .checkbox {
font-weight: 400;
}
.form-donate .form-control {
position: relative;
box-sizing: border-box;
@ -38,19 +27,17 @@ img {
padding: 10px;
font-size: 16px;
}
.form-donate .form-control:focus {
z-index: 2;
}
.form-donate input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-donate input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}

View file

@ -1,13 +0,0 @@
// Set theme to the user's preferred color scheme
function updateTheme() {
const colorMode = window.matchMedia("(prefers-color-scheme: dark)").matches ?
"dark" :
"light";
document.querySelector("html").setAttribute("data-bs-theme", colorMode);
}
// Set theme on load
updateTheme()
// Update theme when the preferred scheme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme)

View file

@ -1,55 +1,42 @@
<!DOCTYPE html>
<html lang="{{ lang }}">
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<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>
<script src="./index.js" defer></script>
</head>
<link rel="icon" href="{{ favicon }}" type="image/x-icon" />
<link href="./css/bootstrap-5.3.3.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous" />
<link href="./css/global.css" rel="stylesheet" />
<script src="https://js.stripe.com/v3/"></script>
<script src="./index.js" defer></script>
<script src="./darkmode.js" defer></script>
</head>
<body class="text-center">
<main class="form-donate">
<div>
<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" 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') }}
</select>
</div>
<body class="text-center">
<main class="form-donate">
<div>
<img src="{{ logo }}" class="mb-4" alt="{{ _('%(name)s Logo', name=name) }}" />
<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-select" id="currency" style="width:40px" aria-label="{{ _('Currency') }}">
{% for iso, symbol in currencies %}
<option value="{{ iso }}">{{ symbol }} </option>
{% endfor %}
</select>
<select class="form-select" id="frequency" aria-label="{{ _('Frequency') }}">
<option value="recuring"> {{ _('/ month') }} </option>
<option value="one_time"> {{ _('one time') }} </option>
</select>
</div>
<button id="submit" class="w-100 btn btn-lg btn-primary">
{{ _('Donate') }}
</button>
<p><a href="{{ contact_url }}">{{ _('If you want to cancel a monthly donation, please contact us') }}</a></p>
<div id="error-message"></div>
</div>
</main>
</body>
</html>
<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>
</body>
</html>

View file

@ -1,32 +1,25 @@
<!DOCTYPE html>
<html lang="{{ lang }}">
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<title>{{ _('Donate to %(name)s', name=name) }}</title>
<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>
<link rel="icon" href="{{ favicon }}" type="image/x-icon" />
<link href="./css/bootstrap-5.3.3.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous" />
<link href="./css/global.css" rel="stylesheet" />
<script src="./darkmode.js" defer></script>
</head>
<body class="text-center">
<main class="form-donate">
<div>
<img src="{{ logo }}" class="mb-4" alt="" width="74" height="74"/>
<h1 class="h3 mb-3 fw-normal">{{ _('Thanks for your donation 🙂') }}</h1>
<body class="text-center">
<main class="form-donate">
<div>
<img src="{{ logo }}" class="mb-4" alt="{{ _('%(name)s Logo', name=name) }}" />
<button onclick="window.location.href = '/';" class="w-100 btn btn-lg btn-primary">{{ _('Go back to the donate form') }}</button>
<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>
</div>
</main>
</body>
</html>
</div>
</main>
</body>
</html>

View file

@ -7,69 +7,48 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-05-22 04:43+0200\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"
"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.15.0\n"
"Generated-By: Babel 2.9.0\n"
#: assets/canceled.html:8 assets/index.html:8 assets/success.html:8
#: assets/canceled.html:7 assets/index.html:7 assets/success.html:7
#, python-format
msgid "Donate to %(name)s"
msgstr ""
#: assets/canceled.html:20 assets/index.html:22 assets/success.html:20
#, python-format
msgid "%(name)s Logo"
msgstr ""
#: assets/canceled.html:22
#: assets/canceled.html:18
msgid "Your payment was canceled"
msgstr ""
#: assets/canceled.html:25 assets/success.html:25
#: assets/canceled.html:20 assets/success.html:20
msgid "Go back to the donate form"
msgstr ""
#: assets/index.html:23
#: assets/index.html:20
#, python-format
msgid "I want to give to %(name)s"
msgstr ""
#: assets/index.html:30
msgid "Amount"
msgstr ""
#: assets/index.html:32
msgid "Currency"
msgstr ""
#: assets/index.html:38
msgid "Frequency"
msgstr ""
#: assets/index.html:39
#: assets/index.html:31
msgid "/ month"
msgstr ""
#: assets/index.html:40
#: assets/index.html:32
msgid "one time"
msgstr ""
#: assets/index.html:45
#: assets/index.html:36
msgid "Donate"
msgstr ""
#: assets/index.html:48
msgid "If you want to cancel a monthly donation, please contact us"
msgstr ""
#: assets/success.html:22
#: assets/success.html:18
msgid "Thanks for your donation 🙂"
msgstr ""

View file

@ -7,72 +7,48 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-05-22 04:43+0200\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"
"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.15.0\n"
"Generated-By: Babel 2.9.0\n"
#: assets/canceled.html:8 assets/index.html:8 assets/success.html:8
#: 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:20 assets/index.html:22 assets/success.html:20
#, python-format
msgid "%(name)s Logo"
msgstr "Logo de %(name)s"
#: assets/canceled.html:22
#: assets/canceled.html:18
msgid "Your payment was canceled"
msgstr "Votre paiement a été annulé"
#: assets/canceled.html:25 assets/success.html:25
#: assets/canceled.html:20 assets/success.html:20
msgid "Go back to the donate form"
msgstr "Retourner au formulaire de don"
#: assets/index.html:23
#: assets/index.html:20
#, python-format
msgid "I want to give to %(name)s"
msgstr "Je veux donner à %(name)s"
#: assets/index.html:30
#, fuzzy
msgid "Amount"
msgstr "Montant"
#: assets/index.html:32
msgid "Currency"
msgstr "Devise"
#: assets/index.html:38
msgid "Frequency"
msgstr "Fréquence"
#: assets/index.html:39
#: assets/index.html:31
msgid "/ month"
msgstr "/ mois"
#: assets/index.html:40
#: assets/index.html:32
msgid "one time"
msgstr "une fois"
#: assets/index.html:45
#: assets/index.html:36
msgid "Donate"
msgstr "Donner"
#: assets/index.html:48
msgid "If you want to cancel a monthly donation, please contact us"
msgstr ""
"Si vous souhaitez mettre fin à votre don mensuel, contactez-nous s'il "
"vous plaît"
#: assets/success.html:22
#: assets/success.html:18
msgid "Thanks for your donation 🙂"
msgstr "Merci pour votre don 🙂"

View file

@ -13,4 +13,3 @@ toml==0.9.6
urllib3==1.25.3
flask-simple-csrf
flask-babel
Werkzeug==0.16.0

View file

@ -12,89 +12,84 @@ import os
import random
import string
from flask import Flask, render_template, jsonify, request, session
from flask_babel import Babel
from flask import Flask, render_template, jsonify, request, send_from_directory, session
from flask_babel import Babel, _
from flask_simple_csrf import CSRF
static_dir = str(os.path.abspath(os.path.join(__file__, "..", "assets")))
app = Flask(
__name__, static_folder=static_dir, static_url_path="", template_folder=static_dir
)
app.config.from_pyfile("settings.py")
stripe.api_key = app.config["STRIPE_SECRET_KEY"]
CSRF = CSRF(config={"SECRET_CSRF_KEY": app.config["SECRET_CSRF_KEY"]})
static_dir = str(os.path.abspath(os.path.join(
__file__, "..", 'assets')))
app = Flask(__name__, static_folder=static_dir,
static_url_path="", template_folder=static_dir)
app.config.from_pyfile('settings.py')
stripe.api_key = app.config['STRIPE_SECRET_KEY']
CSRF = CSRF(config={
'SECRET_CSRF_KEY': app.config['SECRET_CSRF_KEY']
})
app = CSRF.init_app(app)
babel = Babel(app)
@app.before_request
def before_request():
if "CSRF_TOKEN" not in session or "USER_CSRF" not in session:
session["USER_CSRF"] = "".join(
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(64)
)
session["CSRF_TOKEN"] = CSRF.create(session["USER_CSRF"])
if 'CSRF_TOKEN' not in session or 'USER_CSRF' not in session:
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 request.accept_languages.best_match(app.config["LANGUAGES"])
return 'fr' #request.accept_languages.best_match(app.config['LANGUAGES'])
@app.context_processor
def utility_processor():
return dict(lang=babel.locale_selector_func())
@app.route("/", methods=["GET"])
@app.route('/', methods=['GET'])
def get_index():
return render_template(
"index.html", **app.config["CUSTOM"], csrf=session["USER_CSRF"]
)
return render_template('index.html', **app.config['CUSTOM'],
csrf=session['USER_CSRF'])
@app.route("/success", methods=["GET"])
@app.route('/success', methods=['GET'])
def get_success():
return render_template("success.html", **app.config["CUSTOM"])
return render_template('success.html', **app.config['CUSTOM'])
@app.route("/canceled", methods=["GET"])
@app.route('/canceled', methods=['GET'])
def get_canceled():
return render_template("canceled.html", **app.config["CUSTOM"])
return render_template('canceled.html', **app.config['CUSTOM'])
@app.route("/create-checkout-session", methods=["POST"])
@app.route('/create-checkout-session', methods=['POST'])
def create_checkout_session():
data = json.loads(request.data)
domain_url = app.config["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 currencies
or int(data["quantity"]) <= 0
):
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 currencies or \
int(data['quantity']) <= 0:
return jsonify(error="Bad value"), 400
# Create new Checkout Session for the order
price = donation[data["frequency"]][data["currency"]]
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?session_id={CHECKOUT_SESSION_ID}",
success_url=domain_url +
"/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url=domain_url + "/canceled",
payment_method_types=["card"],
payment_method_types= ["card"],
mode=mode,
line_items=[{"price": price, "quantity": data["quantity"]}],
line_items=[
{
"price": price,
"quantity": data['quantity']
}
]
)
return jsonify({"sessionId": checkout_session["id"]})
return jsonify({'sessionId': checkout_session['id']})
except Exception as e:
return jsonify(error=str(e)), 403
if __name__ == "__main__":
app.run(port=app.config["PORT"], debug=app.debug)
if __name__ == '__main__':
app.run(port=app.config['PORT'], debug=app.debug)