mirror of
https://gitlab.com/tildes/tildes.git
synced 2026-04-17 06:48:36 +02:00
Add new version of Stripe Checkout for donating
This commit is contained in:
@@ -31,16 +31,6 @@ server {
|
||||
add_header Strict-Transport-Security "max-age={{ pillar['hsts_max_age'] }}; includeSubDomains; preload" always;
|
||||
{% endif %}
|
||||
|
||||
# Content Security Policy:
|
||||
# - "img-src data:" is needed for Spectre.css icons
|
||||
set $csp_value "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; manifest-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'";
|
||||
|
||||
{% if grains['id'] == 'dev' %}
|
||||
add_header Content-Security-Policy-Report-Only $csp_value always;
|
||||
{% else %}
|
||||
add_header Content-Security-Policy $csp_value always;
|
||||
{% endif %}
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Xss-Protection "1; mode=block" always;
|
||||
@@ -66,7 +56,21 @@ server {
|
||||
# add Expires+Cache-Control headers from the mime-type map defined above
|
||||
expires $expires_type_map;
|
||||
|
||||
# Use a different Content-Security-Policy header for the donation page, to allow
|
||||
# the Stripe javascript file to be loaded from their domain
|
||||
location = /donate_stripe {
|
||||
add_header Content-Security-Policy "default-src 'none'; script-src 'self' https://js.stripe.com; style-src 'self'; img-src 'self' data:; connect-src 'self'; manifest-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'" always;
|
||||
|
||||
try_files $uri @proxy_to_app;
|
||||
gzip_static on;
|
||||
}
|
||||
|
||||
location / {
|
||||
{% if grains['id'] == 'prod' %}
|
||||
# Content Security Policy - "img-src data:" is needed for Spectre.css icons
|
||||
add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; manifest-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'" always;
|
||||
{% endif %}
|
||||
|
||||
# checks for static file, if not found proxy to app
|
||||
try_files $uri @proxy_to_app;
|
||||
gzip_static on;
|
||||
|
||||
@@ -34,7 +34,8 @@ webassets.manifest = json
|
||||
|
||||
# API keys for external APIs
|
||||
api_keys.embedly = embedlykeygoeshere
|
||||
api_keys.stripe = sk_live_ActualKeyShouldGoHere
|
||||
api_keys.stripe.publishable = pk_live_ActualKeyShouldGoHere
|
||||
api_keys.stripe.secret = sk_live_ActualKeyShouldGoHere
|
||||
api_keys.youtube = youtubekeygoeshere
|
||||
|
||||
[server:main]
|
||||
|
||||
10
tildes/static/js/behaviors/stripe-checkout.js
Normal file
10
tildes/static/js/behaviors/stripe-checkout.js
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) 2019 Tildes contributors <code@tildes.net>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
$.onmount("[data-js-stripe-checkout]", function() {
|
||||
/* eslint-disable-next-line no-undef */
|
||||
var stripe = Stripe($(this).attr("data-js-stripe-checkout"));
|
||||
stripe.redirectToCheckout({
|
||||
sessionId: $(this).attr("data-js-stripe-checkout-session")
|
||||
});
|
||||
});
|
||||
@@ -281,7 +281,6 @@ class RateLimitedAction:
|
||||
# the actual list of actions with rate-limit restrictions
|
||||
# each action must have a unique name to prevent key collisions
|
||||
_RATE_LIMITED_ACTIONS = (
|
||||
RateLimitedAction("donate", timedelta(hours=1), 5, max_burst=5, by_user=False),
|
||||
RateLimitedAction("login", timedelta(hours=1), 20),
|
||||
RateLimitedAction("login_two_factor", timedelta(hours=1), 20),
|
||||
RateLimitedAction("register", timedelta(hours=1), 50),
|
||||
|
||||
@@ -21,8 +21,8 @@ _COUNTERS = {
|
||||
"donations": Counter(
|
||||
"tildes_donations_total", "Donation Attempts", labelnames=["type"]
|
||||
),
|
||||
"donation_failures": Counter(
|
||||
"tildes_donation_failures_total", "Donation Failures", labelnames=["type"]
|
||||
"donation_initiations": Counter(
|
||||
"tildes_donation_initiations_total", "Donation Initiations", labelnames=["type"]
|
||||
),
|
||||
"invite_code_failures": Counter(
|
||||
"tildes_invite_code_failures_total", "Invite Code Failures"
|
||||
|
||||
@@ -106,6 +106,10 @@ def includeme(config: Configurator) -> None:
|
||||
# Route to expose metrics to Prometheus
|
||||
config.add_route("metrics", "/metrics")
|
||||
|
||||
# Route for Stripe donation processing page (POSTed to from docs site)
|
||||
config.add_route("donate_stripe", "/donate_stripe")
|
||||
config.add_route("donate_success", "/donate_success")
|
||||
|
||||
# Add all intercooler routes under the /api/web path
|
||||
with config.route_prefix_context("/api/web"):
|
||||
add_intercooler_routes(config)
|
||||
|
||||
@@ -5,18 +5,14 @@
|
||||
|
||||
{% block title %}Stripe donation{% endblock %}
|
||||
|
||||
{% block main_heading %}
|
||||
{% if payment_successful %}
|
||||
Thanks for donating to Tildes!
|
||||
{% else %}
|
||||
Donation failed
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if payment_successful %}
|
||||
<p>You should receive an email receipt. If you have any questions, please feel free to contact <a href="mailto:donate@tildes.net">donate@tildes.net</a></p>
|
||||
{% else %}
|
||||
<p>The Stripe payment failed for some reason (your credit card has not been charged). Please go back to <a href="https://docs.tildes.net/donate-stripe">the donation page</a> and try again. If the payment fails again, please <a href="mailto:donate@tildes.net">send an email</a> so it can be looked into.</p>
|
||||
{% endif %}
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
|
||||
<p>Redirecting to Stripe...</p>
|
||||
|
||||
{# This div will cause the page to redirect to the Stripe Checkout page #}
|
||||
<div
|
||||
data-js-stripe-checkout="{{ publishable_key }}"
|
||||
data-js-stripe-checkout-session="{{ session_id }}"
|
||||
></div>
|
||||
{% endblock %}
|
||||
|
||||
15
tildes/tildes/templates/donate_success.jinja2
Normal file
15
tildes/tildes/templates/donate_success.jinja2
Normal file
@@ -0,0 +1,15 @@
|
||||
{# Copyright (c) 2019 Tildes contributors <code@tildes.net> #}
|
||||
{# SPDX-License-Identifier: AGPL-3.0-or-later #}
|
||||
|
||||
{% extends 'base_no_sidebar.jinja2' %}
|
||||
|
||||
{% block title %}Thanks for donating!{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="empty">
|
||||
<h2 class="empty-title">Thanks for donating to Tildes!</h2>
|
||||
<p class="empty-subtitle">You should receive an email receipt. If you have any questions, please feel free to contact <a href="mailto:donate@tildes.net">donate@tildes.net</a></p>
|
||||
|
||||
<div class="empty-action"><a href="/" class="btn btn-primary">Back to the home page</a></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
68
tildes/tildes/views/donate.py
Normal file
68
tildes/tildes/views/donate.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) 2018 Tildes contributors <code@tildes.net>
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
"""The view for donating via Stripe."""
|
||||
|
||||
import stripe
|
||||
from marshmallow.fields import Float, String
|
||||
from marshmallow.validate import OneOf, Range
|
||||
from pyramid.httpexceptions import HTTPInternalServerError
|
||||
from pyramid.request import Request
|
||||
from pyramid.security import NO_PERMISSION_REQUIRED
|
||||
from pyramid.view import view_config
|
||||
from webargs.pyramidparser import use_kwargs
|
||||
|
||||
from tildes.metrics import incr_counter
|
||||
|
||||
|
||||
@view_config(
|
||||
route_name="donate_stripe",
|
||||
request_method="POST",
|
||||
renderer="donate_stripe.jinja2",
|
||||
permission=NO_PERMISSION_REQUIRED,
|
||||
require_csrf=False,
|
||||
)
|
||||
@use_kwargs(
|
||||
{
|
||||
"amount": Float(required=True, validate=Range(min=1.0)),
|
||||
"currency": String(required=True, validate=OneOf(("CAD", "USD"))),
|
||||
}
|
||||
)
|
||||
def post_donate_stripe(request: Request, amount: int, currency: str) -> dict:
|
||||
"""Process a Stripe donation."""
|
||||
try:
|
||||
stripe.api_key = request.registry.settings["api_keys.stripe.secret"]
|
||||
publishable_key = request.registry.settings["api_keys.stripe.publishable"]
|
||||
except KeyError:
|
||||
raise HTTPInternalServerError
|
||||
|
||||
incr_counter("donation_initiations", type="stripe")
|
||||
|
||||
session = stripe.checkout.Session.create(
|
||||
payment_method_types=["card"],
|
||||
line_items=[
|
||||
{
|
||||
"name": "One-time donation - tildes.net",
|
||||
"amount": int(amount * 100),
|
||||
"currency": currency,
|
||||
"quantity": 1,
|
||||
}
|
||||
],
|
||||
submit_type="donate",
|
||||
success_url="https://tildes.net/donate_success",
|
||||
cancel_url="https://docs.tildes.net/donate-stripe",
|
||||
)
|
||||
|
||||
return {"publishable_key": publishable_key, "session_id": session.id}
|
||||
|
||||
|
||||
@view_config(route_name="donate_success", renderer="donate_success.jinja2")
|
||||
def get_donate_success(request: Request) -> dict:
|
||||
"""Display a message after a successful donation."""
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
# incrementing this metric on page-load and hard-coding Stripe isn't ideal, but it
|
||||
# should do the job for now
|
||||
incr_counter("donations", type="stripe")
|
||||
|
||||
return {}
|
||||
Reference in New Issue
Block a user