Skip to content
Snippets Groups Projects
Commit 98c4c7fe authored by geant-release-service's avatar geant-release-service
Browse files

Finished release 0.10.

parents 399cfef3 2124152a
Branches
Tags 0.10
No related merge requests found
Showing
with 565 additions and 179 deletions
......@@ -15,65 +15,4 @@ service acts as an in-between layer and does support these features
When visitors are registering to TNC2025 in Visit cloud, they are redirected to this service in the
final step
# Development
## Config
This service needs configuation data in the form of a config json file. By default, django look
a file called `config.json` in the current directory. It is possble to override this by setting
the environment variable `CONFIG_FILENAME` to point to a different location. A sample config is
given in `config-example.json`. Make a copy named `config.json` and fill in the required data
such as the `STRIPE_API_KEY` and the `VISIT.api-key` variables.
## Database
Connecting to a database can be done by setting the DATABASE_URL environment variable to a valid
value:
```
export DATABASE_URL=postgresql://<username>:<password>@<host_address>/<database>
```
Altenatively, you can set up a local (sqlite) database. This can be done easily from the
root of this repository
```
python manage-dev.py migrate
```
This will create a database file 'db.sqlite' in the current directory. You can then create a
superuser account
```
python manage-dev.py createsuperuser
```
And finally you need to populate the database with price information. Sample price information
is given in the `prices.json` file, but this file may not be completely up to date
```
python manage-dev.py createprices --file prices.json
```
## Development server
You can then run the development server by running
```
python manage-dev.py runserver
```
## Admin interface
The django admin interface is enabled, so when running the development server you can browse to
`http://127.0.0.1:5000/admin` to login to the admin interface using the credentials you created
using the createsuperuser (or other valid credentials if connected to an existing database). Here
you can manage administrative users, priced items and other things.
## Migrate price information to deployments
with the correct enviroment settings setup, it is possble to dump the latest price information by running from the source database:
```
django-admin dumpdata stripe_checkout.PricedItem |json_pp > prices.json
```
to load price information into the target database, run:
```
django-admin loaddata prices.json
```
See the full documentation on [SWD Documentation](https://swd-documentation.geant.org/stripe-checkout/develop/)
docs/source/_images/visit-bridgeserviceurl.png

36.3 KiB

docs/source/_images/visit-form-redirect.png

26 KiB

.. _administation:
Administration
==============
For administration, log in from the root url ``/`` in the stripe checkout server. Credentials are
available in Lastpass. The following pages are available when logged in:
.. _django-admin:
Django Admin portal (Admin)
---------------------------
.. _manage-users:
Manage Users
############
You can add new users that can login and manage certain aspects of the website. Based on what the
user should be able to do, they need the following permissions:
* Log in: Staff status (Basically every user should have this)
* Access to django admin: Superuser status
* Manage visitors: ``stripe_checkout | order | Manage visitors``
* Download Stripe Report: ``stripe_checkout | report | Can view report``
.. _adding-product-info:
Adding new products or price information to the database
########################################################
Log in to the django admin console and add or update a ``PricedItem``. There are two different kinds
of ``PricedItem``: ``Registration Type`` and ``Extra``
``Registration Type`` items are used to link a visitor's ``registrationType.id`` to a Stripe product
id. Required fields are
* ``Stripe Product id``: The stripe product for this item. Stripe product ids start with ``prod_``
* ``Visit registration id``: The visit ``registrationType.id`` for this registration type. See also
"New registration types" below
``Extra`` items are tied to specific questions and answers in the Visit. A visitor may opt in
(purchase) zero or more extras during their registration, and possibly after. Every extra requires:
* ``Visit checkout question id`` and ``Visit checkout answer id``: a combination of a question and
answer in visit that indicates that the visitor wants to purchase this extra.
* ``Visit paid question id`` and ``Visit paid answer id``: a combination of a question and
answer in visit to indicdate that the visitor has paid for this extra. Upon receiving confirmation
of payment, stripe checkout will set this combination in the visitor's profile.
Aside from the fields described above, the following fields are required for all ``PricedItem``
objects:
* ``Name``: a snake case name that helps with idenfication. This name is not shown to visitors
* ``Display Name``: a user friendly name. This name is shown to visitors in their checkout page and
should match the user facing name in Visit and Stripe
* ``Kind``: See above
Migrate price information between deployments
#############################################
With the correct enviroment settings setup, it is possble to dump the latest price information by
running from the source database::
django-admin dumpdata stripe_checkout.PricedItem | json_pp > prices.json
To load price information into the target database, change your database settings and run::
django-admin loaddata prices.json
GBP Vat
#######
GBP VAT rates are stored in the Exchange rates section. This is done automatically by the
:ref:`getexchangerate<getexchangerate>` management command that is run periodically.
.. _manage_visitors:
Manage Visitors
---------------
A special page is made to quickly manage visitors (``/visitors``). Here it is possible to change
what a Visitor has checked out and paid for. it is also possible to clear any ``Order``\s that have
been created for the visitor. This is useful during development and testing, but should not be used
in production. Due to limitations of the Visit API, this page only shows the first 100 registered
visitors.
.. warning::
Be careful, it is very easy to accidentally make a mistake when editing data through the
``/visitors`` endpoint. This may cause visitors to be registered incorrectly and to have an
incorrect payment status
.. _download_stripe_report:
Download Stripe Report
----------------------
Finance has requested a (periodic) report of all invoices and credit notes in Stripe. Stripe does
not offer a reporting functionality sufficient for Finance, so every day a report is generated
in a csv format using the ``generatereport`` management command.
\ No newline at end of file
......@@ -58,7 +58,7 @@ def setup(app):
# TODO: give this a better project name
project = "stripe-checkout"
copyright = "2022, GÉANT Vereniging"
copyright = "2025, GÉANT Vereniging"
author = "swd@geant.org"
# The full version, including alpha/beta/rc tags
......
Development
===========
.. _installation:
Installation
------------
Install the package and dependencies::
pip install -r requirements.txt
pipe install -e .
Create a ``config.json`` file::
cp config-example.json config.json
Fill out the ``STRIPE_API_KEY`` and ``VISIT_API_KEY`` settings.
Setup a local (sqlite) database::
python manage-dev.py migrate
python manage-dev.py createsuperuser
python manage-dev.py loaddata prices-test.json
This will create a file ``db.sqlite`` and initialize it with the necessary tables and data. You
can now run a development server by running::
python manage-dev.py runserver
.. _invoking-management-commands:
Invoking management commands
----------------------------
The ``manage-dev.py`` file is an entrypoint to running django management commands. The generic CLI
tool for running
`django-admin<https://docs.djangoproject.com/en/4.2/ref/django-admin/>`
This command requires at least one enviroment variable to be set ``DJANGO_SETTINGS_MODULE`` to
point to a python module for the project's settings as a dotted path. For local development, the
path should be ``stripe_checkout.settings.dev``. Inside the root of the ``stripe_checkout`` repository,
The ``manage-dev.py`` file is preconfigured with this enviroment variable, and a ``CONFIG_FILENAME``
environment variable pointing to the ``config.json`` file created during :ref:`installation`.
When invoking management commands in the deployed VMs you should use the ``/home/tnc/cmd.sh``
script instead of ``django-admin``. This script is configured with the correct environment variables
for running django commands against the deployed instance.
Settings
--------
There are three ways of supplying settings. These are described below
Settings file
#############
For development, use the ``stripe_checkout.settings.dev`` settings module. Deployed environments
(test, uat, prod) use the ``stripe_checkout.settings.prod`` settings module. These files contain
settings that are static between the environments, and contain some logic for reading and parsing
environment variables that are set for specific environments.
.. _config_json:
Config file
###########
The config file is a way to supply settings that are non-static, and therefore cannot be placed
in a settings file. The config file must be a json file containing a top level dictionary, where
the keys are settings (are therefore must be ``UPPER_CASE``) and are directly interpreted as django
settings. This allows the values to have an arbitrary structure other than a single string, such
as an array. The config file is validated to a json schema, located in ``stripe_checkout.config``.
In order to use a specific config file, set the ``CONFIG_FILENAME`` environment variable to a file
path ``path/to/config.json`` that points to a valid config file. This can be an absolute path or
a relative path. For discoverability, it is better to use an absolute path.
Environment Variables
#####################
We use the ``DJANGO_SETTINGS_MODULE`` and ``CONFIG_FILENAME`` to point to the settings file
``dotted.path`` and config file ``path/to/config.json``. There are also other environment variables
for settings that cannot be static in a settings file, because they either vary between environments,
or contain sensitive information. The settings files are set up in a way to read certain enviroment
variables and use them for specific settings.
.. note::
Both the ``config.json`` and environment variables can be used for variable settings. When to use one
over the other is somewhat arbitrary, but there are guidelines. Settings that are relevant to the
application specifics should be placed in the ``config.json``, such as api keys and specific
stripe or visit ids. Also settings that have a more complex structure than a single string (such
as an array) are more easily set using a ``config.json`` Settings that have a more
infrastructure-like nature, or have to do with django internals, such as ``DJANGO_FRONTEND_URL``
or ``DJANGO_SECRET_KEY`` are supplied using environment variables. Another reason to use an
environment variable is when a value needs to be processed before being assigned to the actual
setting, as is the case with the ``DATABASE_URL`` environment variable.
Database
--------
Stripe checkout needs a database to function properly. In development this is a sqlite database.
You can also connect to the test database by setting the ``DATABASE_URL`` environment variable
(substitute the username and password with the data in Lastpass)::
export DATABASE_URL=postgres://<username>:<password>@test-postgres.geant.org/stripe_checkout
If you now run ``django-admin`` or ``manage-dev.py`` this will connect to the database for the
test deployment.
Email
-----
``stripe-checkout`` sends an email when it sees a succeeded payment intent that doesn't match an
``Order`` in the database, and therefore can't link it to a visitor's purchase. It uses Django's
`send_mail` functionality. Django uses the following settings:
- `EMAIL_HOST` (default: `localhost`)
- `EMAIL_PORT` (default: `25`)
It so happens postfix is configured on the ``stripe-checkout`` vm's to forward to the geant smtp
servers. So we don't need much additional configuration. We only configure the `DEFAULT_FROM_EMAIL`
setting. The recipients for these emails can be set using the ``UNPROCESSED_PAYMENT_EMAIL_TO``
setting in the :ref:`config_json`
.. _stripe_webhook_endpoint:
Stripe webhook endpoint
-----------------------
Stripe can be configured to call a `webhook endpoint <https://docs.stripe.com/webhooks>`_ for certain
Stripe events. These webhooks can be configured in the `Stripe Workbench <https://dashboard.stripe.com/workbench/webhooks>`_.
Stripe is configured to call the ``/stripe-event-webhook/`` endpoint for ``payment_intent.succeeded``
and ``payment_intent.canceled`` webhooks (see also: :ref:`design`). There may be other events
configured as well, but these are ignored when :ref:`processing events <processevents>`.
During development it is possible to register your locally running development server as an event
webhook using the `stripe cli <https://docs.stripe.com/stripe-cli>`_ and register a
`local listener <https://docs.stripe.com/webhooks#local-listener>`_. After setting up stripe cli and
logging in, you can hook up the local developer server with the following command::
stripe listen --events payment_intent.succeeded,payment_intent.canceled \
--forward-to 127.0.0.1:8000/stripe-event-webhook/
Upon running this command, the stripe cli will output a stripe signing secret. This is a secret key
that stripe will use to sign the event payload so that we can be certain the events are actually
coming from Stripe. Add this secret to you ``config.json`` as the ``STRIPE_SIGNING_SECRET`` setting
and restart the development server.
``Events``\s will now be registered in the local database. Run the following command to process these
events::
python manage-dev.py processevents
.. note::
One way the ``/stripe-event-webhook`` is secured is through the signing secret. The second way
this endpoint is secured, is that only IP addresses from stripe can actually call this endpoint.
The full list of stripe webhook IP addresses can be found
`here <https://docs.stripe.com/ips#webhook-notifications>`_. These IP addresses are placed in the
``STRIPE_WEBHOOK_ALLOWED_IPS`` setting in the ``stripe_checkout.settings.prod`` settings module.
.. _visit-redirect:
Visit redirect
--------------
The redirect to the stripe checkout server in Visit is done using a custom javascript snippet:
1. Click the final page "Complete"
2. Click "current page settings"
3. Clikc "Edit Javascript"
.. image:: _images/visit-form-redirect.png
:alt: Visit form redirect instructions
Redirect URL
############
The redirect url is stored in the Visit custom field ``bridgeserviceurl``. To edit this, login to
Visit, open the TNC 2025 event and follow these steps:
1. Click on Event
2. Click on Setup
3. Click on Custom fields
.. image:: _images/visit-bridgeserviceurl.png
:width: 700
:alt: Visit Redirect URL
For more information on custom fields, see the Visit `documentation <https://help.visitcloud.com/create/docs/user-guide/organisation/edit-the-organisation/#add-and-edit-custom-fields>`_
\ No newline at end of file
Endpoints
=========
Visitor endpoints
-----------------
The following endpoints are configured in this service
``/checkout/<visitor_id>/``
###########################
The main entry point for users. Here they can configure a PO and VAT number and confirm their
order. Upon confirmation they get redirected to Stripe.
``/checkout/stripe-event-webhook/``
###################################
This endpoint is configured as a stripe event webhook, so that we get updated whenever
a payment (credit card or bank transfer) has succeeded and we can update the visitors Paid
status in Visit. It is secured using `Stripes signing method
<https://docs.stripe.com/webhooks#verify-official-libraries>`_. See also
:ref:`stripe_webhook_endpoint`
Administrative endpoints
------------------------
``/admin/``
###########
Access to the Django admin interface. Here you can add new users, and manage PricedItems and
Orders. See :ref:`django-admin`
``/visitors/``
##############
Allow for quickly setting a visitor's registration type, extras and paid status in Visit. See
:ref:`manage_visitors`
``/report/``
##############
Download a daily generated report with all invoices and credit notes in Stripe. See
:ref:`download_stripe_report`
.. gateway intro
Visit Stripe Gateway
====================
This service acts as a gateway between Visit(cloud) and Stripe and its main purpose is to be able
to customize the checkout process for people registering for TNC2025 and enable the following
features:
* Payment through bank transfer
* Addition of PO and VAT number in the invoice
These features are not supported by Visit's implementation of the Stripe integration. This
service acts as an in-between layer and does support these features
When visitors are registering to TNC2025 in Visit cloud, they are redirected to this service in the
final step of the registration form. Here they get the opportunity to fill out a purchase order
and/or VAT number and select wether to pay using credit card or bank transfer. They then get
redirected to the relevant stripe web page
Design
------
This gateway service is designed in two parts. The first part is a Django app that takes care
of user interaction, setting up the Stripe payment objects and redirecting the users. When it
creates a payment object (either a checkout Session or a PaymentIntent), it also creates a database
entry (name ``Order``) that links the stripe payment object back to the Visit visitor (ie. the
registrant). It does not directly update the visitor's paid status in Visit. Whenever the visitor
has fulfilled its payment, Stripe will call a webhook in our service (see below) to notify us of
this fact. We store this ``Event`` in our database and process it asynchronously. Stripe requires
us to handle the webhook request fast, so we cannot directly update the status in Visit.
The second part of the design is a worker script that checks the databases for any new ``Event``
entries, and performs the necessary updates in our database and in Visit through the Visit API.
This worker is configured as a cronjob
Endpoints
---------
The following endpoints are configured in this service
``/checkout/<visitor_id>/``
###########################
The main entry point for users. Here they can configure a PO and VAT number and confirm their
order. Upon confirmation they get redirected to Stripe.
``/checkout/<visitor_id>/success/``
###################################
Used as a redirect by stripe on a successful payment. Because this endpoint not being secure, we
do not update the Visit paid status.
``/checkout/<visitor_id>/cancel/``
##################################
Used as a redirect by stripe on a canceled payment
``/checkout/stripe-event``
##########################
This endpoint is configured as a stripe event webhook, so that we get updated whenever
a payment (credit card or bank transfer) has succeeded and we can update the visitors Paid
status in Visit. It is secured using Stripes signing secret method (cf.
`https://docs.stripe.com/webhooks#verify-official-libraries`_
)
Administrative endpoints
------------------------
/visitors/
##########
Allow for quickly setting a visitor's shopping cart and paid items in Visit.
/admin/
#######
Access to the Django admin interface. Here you can add new users, and manage PricedItems and
Orders
stripe_checkout
========================================
Placeholder for docs
.. toctree::
:maxdepth: 2
:caption: Contents:
gateway
introduction
endpoints
administration
development
\ No newline at end of file
.. _introduction:
Introduction
============
This service acts as a gateway between Visit(cloud) and Stripe and its main purpose is to be able
to customize the checkout process for people registering for TNC2025 and enable the following
features:
* Payment through bank transfer or credit card
* Customization of the invoice
* allow the visitors to set a PO number and VAT number
* add the VAT in GBP as well as EUR
* Use a specific invoice template
These features are not supported by Visit's implementation of the Stripe integration. This
service acts as an in-between layer and does support these features
When visitors are registering to TNC2025 in Visit cloud, they are redirected to this service in the
final step of the registration form. Here they get the opportunity to fill out a purchase order
and/or VAT number. Upon clicking a confirmation button an invoice is created for them and they are
redirected to Stripe where they can view the invoice, pay directly by credit card and/or see
payment instructions for paying by bank transfer. They also receive an e-mail with all payment
information
``
.. note::
Throughout this documentation, there are instructions to invoke django management commands. These
instructions use the ``django-admin`` cli tool to invoke these commands. Depending on the
situation you may want to use another script to invoke these commands, such as ``manage-dev.py``
or ``cmd.sh``. See also :ref:`invoking-management-commands`
.. _design:
Design
------
The design of the stripe-checkout service is roughly as following:
Whenever a visitor registers for TNC, they fill out a form in Visit. This form sets their
``registrationType``. While filling out the form, the visitor may also want to purchase some extras,
such as access to side meetings or social events. The information for extras is stored in the
visitors profile as answers to certain questions. For example, there is a question for "Side
meetings" with answers "Friday" and "Sunday"
* As the final page in the registration form, the visitor is redirected to the stripe checkout
service. Here they see what they've checked out and how much they need to pay.
* The checkout service reads the visitor's profile through the Visit API (the visitor is
identified by their visitor id in the url).
* From the visitor's profile, the Stripe products that the visitor needs to pay for are
determined. A visitor can have a single Registration Type item and multiple extras. Every
category of extras has two questions: One that the visitor answers with the extras they want,
and one that is goverened by the stripe checkout service when the visitor's payment has been
processed. Every answer in the paid-questions represents an extra that the visitor has paid for.
These are defined as ``PricedItem`` objects in the database. See also: :ref:`adding-product-info`
* All items that the visitor has not previously purchased (see ``Order`` below) are added to a
``ShoppingCart`` and this collection is shown to the user in their checkout page
* In their checkout page, the visitor can add some additional information that will be shown on
the invoice. These are a Purchase Order (PO) number and/or a VAT id. These fields are optional.
Then the visitor clicks Confirm to finalize the purchase
* The stripe checkout service now creates an invoice in Stripe for the items that they need to pay
for:
* All ``PricedItem`` objects that the visitor needs to pay for are collected in an ``Order`` and
stored in the database. Any items for which the visitor already has an ``Order`` are excluded from
the new ``Order``, so that the visitor is charged only once for an item.
* The visitor's billing address is read from their Visit profile. ``contact.addresses`` should
contain an address object of ``"type": "billing"``.
.. note::
In order for any billing information to be
shown in the Invoice, it is required that the ``country`` field is set to a valid country
code in the billing address.
* The visitor is identified in stripe by their email address. If the visitor's email address
is already bound to a customer in Stripe, we use that customer, but update the billing details
using their Visit profile
* Additional fields are added to the the invoice as ``custom_fields``. These are:
* Purchase Order: Supplied by the visitor (optional)
* VAT number: Supplied by the visitor (optional)
* GBP VAT: The VAT amount in GBP. The exchange rate read from the database and is periodically
updated by management command ``getexchangerate``
* We store the ``visitor_id`` and ``order_id`` as metadata fields to the invoice. That way we can
always find back the order if we have the Stripe invoice.
* We tell Stripe to finalize and send the invoice by email to the visitor. Stripe also generates
a url where the visitor can directly pay their invoice. We redirect the user to this url. The
visitor facing part of the stripe checkout service is now finished.
* When creating and finalizing the Invoice, Stripe also creates a payment intent. Before
redirecting the visitor to their Stripe payment page, we store the payment intent's id
(starting with ``pi_``) along with the ``Order`` so that we can reference it later.
* We now wait for the visitor to pay. The visitor may pay by credit card or by bank transfer. While
credit card payments are processed almost instantly, bank transfers may take time to be
processed. First, the visitor needs to actually pay, and then Stripe needs to process the
payment. The total time between placing the order and strip processing the payment may take weeks
or longer.
* When the visitor's payment has been succesfully processed by Stripe, Stripe calls our webhook at
the ``stripe-event-webhook/`` endpoint. This endpoint does not process the event, but merely stores
it in the database as an Event. Stripe requires that this endpoint returns quickly so processing
the events happens asynchronously by periodically running the django managemement command
``processevents``
.. _processevents:
Processing stripe events
########################
* The ``processevents`` command looks in the database for Events that are not processed yet. It
looks at two types of events: ``payment_intent.succeeded`` and ``payment_intent.canceled``.
* ``payment_intent.succeeded`` events indicate that the visitor has paid. We look in the ``Order``
table for a matching ``Order``, and update the visitor's Profile in Visit. We add the ``PAID``
tag to the visitor to indicate that they've paid for their main registration, and add the relevant
answers to the "paid"-question for extra's. In certain cases there is no ``Order`` that is
linked to the payment_intent. This can happen in the following circumstances and we handle
these differently
* If during processing of the invoice, the invoice (and payment intent) gets created, but for
some reason we are unable to write back the payment intent id to the ``Order``, we end up
with an ``Order`` without a payment intent reference. In this case we do a reverse lookup. We
retrieve the invoice from the payment intent and look in the metadata for a ``visitor_id`` and
and ``order_id``. If we can find an order that match those parameters we use it and continue
as normal
* If the original invoice was credited and another invoice was created manually, this has a new
payment intent attached to it. The ``Order`` now has an incorrect payment intent reference.
We try the reverse lookup using the invoice's metadata, but if that fails, we send an email
with a notification that we encountered a payment intent that we cannot process. The
payment_intent / invocie must be processed manually
* ``payment_intent.canceled`` events happen when an invoice was credited. We add the
``CANCELLED`` tag to the visitor, to indicate that we now no longer expect them to pay
.. _getexchangerate:
Fetching exchange rates
#######################
* Another job that runs periodically is the ``getexchangerate`` command. Every day it looks for a
new GBP/EUR exchange rate on
`https://www.trade-tariff.service.gov.uk/exchange_rates <https://www.trade-tariff.service.gov.uk/exchange_rates>`_
and if it finds one, adds it to the database to be used from now on. The trade-tariff service
publishes the exchange rates every month.
......@@ -49,7 +49,7 @@
"display_name" : "Side meeting (Friday)",
"kind" : "EXTRA",
"name" : "side_meeting_pass_friday",
"stripe_product_id" : "prod_RZw4eSzXLC1A2N",
"stripe_product_id" : "prod_RZw4cMh6B1tL3T",
"visit_checkout_answer_id" : "1zeb3k6yy",
"visit_checkout_question_id" : "1zeb2vc8p",
"visit_paid_answer_id" : "1zen12x8z",
......@@ -64,7 +64,7 @@
"display_name" : "Side meeting (Monday)",
"kind" : "EXTRA",
"name" : "side_meeting_pass_monday",
"stripe_product_id" : "prod_RPrt4MzT2m98d2",
"stripe_product_id" : "prod_RZw4eSzXLC1A2N",
"visit_checkout_answer_id" : "1zeb3k6yx",
"visit_checkout_question_id" : "1zeb2vc8p",
"visit_paid_answer_id" : "1zen12x8y",
......@@ -118,5 +118,20 @@
},
"model" : "stripe_checkout.priceditem",
"pk" : 9
},
{
"fields" : {
"display_name" : "PC Pass",
"kind" : "REGISTRATION_TYPE",
"name" : "pc_pass",
"stripe_product_id" : "prod_Ria6hn9u7weTad",
"visit_checkout_answer_id" : "",
"visit_checkout_question_id" : "",
"visit_paid_answer_id" : "",
"visit_paid_question_id" : "",
"visit_registration_id" : "1zeurvsr3"
},
"model" : "stripe_checkout.priceditem",
"pk" : 10
}
]
......@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="stripe-checkout",
version="0.9",
version="0.10",
author="GEANT",
author_email="swd@geant.org",
description="Stripe custom checkout support service",
......
......@@ -13,6 +13,10 @@ CONFIG_SCHEMA = {
"STRIPE_TAX_RATE_ID": {"type": "string"},
"VISIT_API_KEY": {"type": "string"},
"VISIT_EXPO_ID": {"type": "string"},
"UNPROCESSED_PAYMENT_EMAIL_TO": {
"type": "array",
"items": {"type": "string"},
},
},
"required": [
"STRIPE_API_KEY",
......
from typing import Sequence
from django.http import Http404, HttpResponse
from django.shortcuts import redirect, render
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import login_required, permission_required
from stripe_checkout.stripe_checkout.models import ItemKind, Order, PricedItem, Report
from . import visit
......@@ -14,6 +14,7 @@ def index(request):
@login_required
@permission_required("stripe_checkout.manage_visitors")
@require_GET
def list_visitors(request):
api = visit.VisitorAPI()
......@@ -37,6 +38,7 @@ def list_visitors(request):
@login_required
@permission_required("stripe_checkout.manage_visitors")
@require_POST
def update_visitor(request, visitor_id):
all_extras: Sequence[PricedItem] = (
......@@ -65,6 +67,7 @@ def update_visitor(request, visitor_id):
@login_required
@permission_required("stripe_checkout.manage_visitors")
@require_POST
def delete_orders(request, visitor_id):
Order.objects.filter(visitor_id=visitor_id).delete()
......@@ -72,6 +75,7 @@ def delete_orders(request, visitor_id):
@login_required
@permission_required("stripe_checkout.view_report")
@require_GET
def get_report(request):
report = Report.objects.first()
......
# Generated by Django 4.2.18 on 2025-02-13 14:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("stripe_checkout", "0008_order_created_alter_order_stripe_id"),
]
operations = [
migrations.AlterModelOptions(
name="order",
options={"permissions": (("manage_visitors", "Manage visitors"),)},
),
]
......@@ -83,6 +83,9 @@ class Order(models.Model):
)
created = models.DateTimeField(auto_now_add=True)
class Meta:
permissions = (("manage_visitors", "Manage visitors"),)
class Event(models.Model):
payload = models.JSONField(blank=False)
......
......@@ -4,14 +4,20 @@
{% endblock title %}
{% block body %}
<ul>
<li>
<a href="{% url 'admin:index' %}">Admin</a>
</li>
<li>
<a href="{% url 'stripe_checkout:list-visitors' %}">Manage visitors</a>
</li>
<li>
<a href="{% url 'stripe_checkout:get-report' %}">Download Stripe Report</a>
</li>
{% if user.is_superuser %}
<li>
<a href="{% url 'admin:index' %}">Admin</a>
</li>
{% endif %}
{% if perms.stripe_checkout.manage_visitors %}
<li>
<a href="{% url 'stripe_checkout:list-visitors' %}">Manage visitors</a>
</li>
{% endif %}
{% if perms.stripe_checkout.view_report %}
<li>
<a href="{% url 'stripe_checkout:get-report' %}">Download Stripe Report</a>
</li>
{% endif %}
</ul>
{% endblock body %}
{% extends "tnc-base.html" %}
{% block title %}
Invoice sent
{% endblock title %}
{% block content %}
<div class="checkout flex center">
<div class="card flex center column">
<p>
Thank you for registering for TNC 2025! You should soon receive an email with an
invoice for your registration as well as payment instructions. If not, please check
your junk mail. After you have successfully paid the invoice, your registration
will be completed.
</p>
</div>
</div>
{% endblock content %}
......@@ -21,7 +21,7 @@
{% for visitor in visitors %}
<tr>
<td>
<a href="{% url 'stripe_checkout:checkout-entrypoint' visitor.id %}"
<a href="{% url 'stripe_checkout:checkout' visitor.id %}"
target="_blank">{{ visitor.id }}</a>
</td>
<td>{{ visitor.contact.firstName }} {{ visitor.contact.lastName }}</td>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment