pretix-google-wallet 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pretix_google_wallet-0.9.0/LICENSE +21 -0
- pretix_google_wallet-0.9.0/MANIFEST.in +4 -0
- pretix_google_wallet-0.9.0/PKG-INFO +154 -0
- pretix_google_wallet-0.9.0/README.md +125 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/__init__.py +7 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/apps.py +37 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/client.py +151 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/locale/de/LC_MESSAGES/django.po +144 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/migrations/0001_initial.py +40 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/migrations/__init__.py +0 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/models.py +22 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/signals.py +62 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/tasks.py +59 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/templates/pretix_google_wallet/settings.html +20 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/ticketoutput.py +115 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet/wallet.py +353 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/PKG-INFO +154 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/SOURCES.txt +25 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/dependency_links.txt +1 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/entry_points.txt +2 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/requires.txt +9 -0
- pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/top_level.txt +1 -0
- pretix_google_wallet-0.9.0/pyproject.toml +70 -0
- pretix_google_wallet-0.9.0/setup.cfg +9 -0
- pretix_google_wallet-0.9.0/setup.py +24 -0
- pretix_google_wallet-0.9.0/tests/test_wallet.py +198 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stefan Bethke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pretix-google-wallet
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: pretix ticket output that adds tickets to Google Wallet
|
|
5
|
+
Author-email: Stefan Bethke <stb@lassitu.de>
|
|
6
|
+
Maintainer-email: Stefan Bethke <stb@lassitu.de>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: homepage, https://github.com/stblassitude/pretix-google-wallet
|
|
9
|
+
Project-URL: repository, https://github.com/stblassitude/pretix-google-wallet
|
|
10
|
+
Project-URL: issues, https://github.com/stblassitude/pretix-google-wallet/issues
|
|
11
|
+
Keywords: pretix,google wallet,tickets
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Intended Audience :: Other Audience
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: PyJWT[crypto]>=2
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-django; extra == "dev"
|
|
25
|
+
Requires-Dist: responses; extra == "dev"
|
|
26
|
+
Requires-Dist: flake8; extra == "dev"
|
|
27
|
+
Requires-Dist: isort; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# pretix Google Wallet
|
|
31
|
+
|
|
32
|
+
A [pretix](https://pretix.eu) ticket output plugin that lets attendees add their tickets to
|
|
33
|
+
[Google Wallet](https://wallet.google/). It is the Android counterpart of
|
|
34
|
+
[pretix-passbook](https://github.com/pretix/pretix-passbook) (Apple Wallet).
|
|
35
|
+
|
|
36
|
+
Next to the PDF download, customers get an **Add to Google Wallet** button for every ticket. Unlike Apple
|
|
37
|
+
Wallet passes, Google Wallet passes stay linked to the issuer, so this plugin keeps them up to date:
|
|
38
|
+
|
|
39
|
+
- Canceling, expiring or denying an order (or canceling a single position) turns the pass inactive.
|
|
40
|
+
Reactivating the order turns it active again.
|
|
41
|
+
- Changes to attendee names, products or seats are pushed to the pass.
|
|
42
|
+
- Changes to the event name, dates or location are pushed to all passes of the event (checked every 30 minutes
|
|
43
|
+
for upcoming events).
|
|
44
|
+
|
|
45
|
+
Each pass shows the event name, date and doors-open time, venue, attendee name, product and variation, seat
|
|
46
|
+
(for seated events), order code, a QR code with the ticket secret (scannable by pretix check-in apps), and a
|
|
47
|
+
link to the ticket page. Optionally it shows the price and a free-text information block. Event series are
|
|
48
|
+
supported: every date gets its own Google Wallet class.
|
|
49
|
+
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- pretix 2025.1 or newer, self-hosted
|
|
53
|
+
- A Google Wallet issuer account and a Google Cloud service account (see below)
|
|
54
|
+
- Your pretix installation must be reachable from the internet over HTTPS if you upload a logo or banner image,
|
|
55
|
+
because Google fetches the images from pretix.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
Install the package into the Python environment of your pretix installation, then run the migrations and
|
|
60
|
+
restart pretix (web server and Celery workers):
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
pip install pretix-google-wallet # or: pip install git+https://github.com/stblassitude/pretix-google-wallet
|
|
64
|
+
python -m pretix migrate
|
|
65
|
+
python -m pretix rebuild
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Setting up Google Wallet
|
|
69
|
+
|
|
70
|
+
1. **Issuer account.** Open the [Google Pay & Wallet Console](https://pay.google.com/business/console), create a
|
|
71
|
+
business profile and activate the *Google Wallet API*. Note the numeric **Issuer ID**.
|
|
72
|
+
2. **Service account.** In the [Google Cloud console](https://console.cloud.google.com/), create (or pick) a
|
|
73
|
+
project, enable the **Google Wallet API**, create a **service account** and add a **JSON key** for it.
|
|
74
|
+
Download the key file.
|
|
75
|
+
3. **Grant access.** In the Pay & Wallet Console, go to *Users* and invite the service account's e-mail
|
|
76
|
+
address (`…@….iam.gserviceaccount.com`) with *Developer* access.
|
|
77
|
+
4. **Test, then publish.** A new issuer account is in demo mode: only Google accounts added as test users in
|
|
78
|
+
the console can save passes, and passes show a "TEST ONLY" label. When everything works, request publishing
|
|
79
|
+
access in the console. Google reviews your account before passes become available to everyone.
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
Enable the plugin for an event (*Settings → Plugins → Output and export formats → Google Wallet*), then
|
|
84
|
+
configure it under *Settings → Tickets → Google Wallet*:
|
|
85
|
+
|
|
86
|
+
| Setting | Description |
|
|
87
|
+
|-----------------------|-------------------------------------------------------------------------------------|
|
|
88
|
+
| Issuer ID | The numeric issuer ID from the Pay & Wallet Console. |
|
|
89
|
+
| Service account key | The contents of the JSON key file. |
|
|
90
|
+
| Issuer name | Shown at the top of the pass. Defaults to the organizer name. |
|
|
91
|
+
| Logo | Square PNG/JPEG, shown as a circle. 660×660 px recommended. |
|
|
92
|
+
| Banner image | Wide PNG/JPEG on the front of the pass. 1032×336 px recommended. |
|
|
93
|
+
| Background color | Pass color, e.g. `#3b1c4a`. Defaults to the event's primary color. |
|
|
94
|
+
| Show ticket price | Adds the price to the pass. |
|
|
95
|
+
| Additional information| Free text (multi-language) shown in the pass details. |
|
|
96
|
+
|
|
97
|
+
### Installation-wide credentials
|
|
98
|
+
|
|
99
|
+
On a self-hosted installation you usually want one set of credentials for all events. Add them to
|
|
100
|
+
`pretix.cfg`; the event-level fields then become optional and override the global values only when filled in:
|
|
101
|
+
|
|
102
|
+
```ini
|
|
103
|
+
[googlewallet]
|
|
104
|
+
issuer_id=3388000000012345678
|
|
105
|
+
service_account_file=/etc/pretix/google-wallet-key.json
|
|
106
|
+
; Optional: prefix for class and object IDs, default "pretix". Set this if several
|
|
107
|
+
; pretix installations share one issuer account.
|
|
108
|
+
id_prefix=pretix
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
As with all pretix settings, you can use environment variables instead, e.g. `PRETIX_GOOGLEWALLET_ISSUER_ID`,
|
|
112
|
+
and `PRETIX_GOOGLEWALLET_SERVICE_ACCOUNT` (the JSON itself) or `FILE__PRETIX_GOOGLEWALLET_SERVICE_ACCOUNT`
|
|
113
|
+
(a file containing it). The key file must be readable by the user pretix runs as.
|
|
114
|
+
|
|
115
|
+
## How it works
|
|
116
|
+
|
|
117
|
+
When a customer clicks *Add to Google Wallet*, pretix (in a Celery worker) creates or updates the event's
|
|
118
|
+
*event ticket class* and the ticket's *event ticket object* through the Google Wallet REST API, then redirects
|
|
119
|
+
the customer to a signed `https://pay.google.com/gp/v/save/…` link referencing that object.
|
|
120
|
+
|
|
121
|
+
IDs are derived from the event and ticket, e.g. class `<issuer>.pretix-<organizer>-<event>[-<date id>]` and
|
|
122
|
+
object `<issuer>.pretix-<organizer>-<event>-<order code>-<position id>`. The plugin stores which tickets have
|
|
123
|
+
a Google Wallet object, and only those orders cause API calls when they change.
|
|
124
|
+
|
|
125
|
+
The plugin only depends on `requests` and `PyJWT`, which pretix already ships; the Google client libraries are
|
|
126
|
+
not needed.
|
|
127
|
+
|
|
128
|
+
## Limitations
|
|
129
|
+
|
|
130
|
+
- Only Google Wallet is supported. Samsung Wallet and other wallets use different APIs. For Apple Wallet,
|
|
131
|
+
use [pretix-passbook](https://github.com/pretix/pretix-passbook).
|
|
132
|
+
- One button per ticket; there is no "add all tickets of the order" button, and passes are not attached to
|
|
133
|
+
e-mails (pretix skips redirect-type outputs for attachments).
|
|
134
|
+
- The ticket preview in the backend is disabled, because it would create real objects in Google Wallet.
|
|
135
|
+
- Check-ins are not reflected on the pass.
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
uv venv --python 3.12 .venv
|
|
141
|
+
uv pip install --python .venv/bin/python pretix -e '.[dev]'
|
|
142
|
+
.venv/bin/pytest
|
|
143
|
+
.venv/bin/flake8 pretix_google_wallet tests && .venv/bin/isort -c pretix_google_wallet tests
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The tests mock the Google APIs with `responses`. To update translations, run
|
|
147
|
+
`django-admin makemessages -l de --no-location --no-obsolete` inside `pretix_google_wallet/` with
|
|
148
|
+
`DJANGO_SETTINGS_MODULE=pretix.testutils.settings`.
|
|
149
|
+
|
|
150
|
+
Releases are built by GitHub Actions when a `v*` tag is pushed and published to PyPI via trusted publishing.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# pretix Google Wallet
|
|
2
|
+
|
|
3
|
+
A [pretix](https://pretix.eu) ticket output plugin that lets attendees add their tickets to
|
|
4
|
+
[Google Wallet](https://wallet.google/). It is the Android counterpart of
|
|
5
|
+
[pretix-passbook](https://github.com/pretix/pretix-passbook) (Apple Wallet).
|
|
6
|
+
|
|
7
|
+
Next to the PDF download, customers get an **Add to Google Wallet** button for every ticket. Unlike Apple
|
|
8
|
+
Wallet passes, Google Wallet passes stay linked to the issuer, so this plugin keeps them up to date:
|
|
9
|
+
|
|
10
|
+
- Canceling, expiring or denying an order (or canceling a single position) turns the pass inactive.
|
|
11
|
+
Reactivating the order turns it active again.
|
|
12
|
+
- Changes to attendee names, products or seats are pushed to the pass.
|
|
13
|
+
- Changes to the event name, dates or location are pushed to all passes of the event (checked every 30 minutes
|
|
14
|
+
for upcoming events).
|
|
15
|
+
|
|
16
|
+
Each pass shows the event name, date and doors-open time, venue, attendee name, product and variation, seat
|
|
17
|
+
(for seated events), order code, a QR code with the ticket secret (scannable by pretix check-in apps), and a
|
|
18
|
+
link to the ticket page. Optionally it shows the price and a free-text information block. Event series are
|
|
19
|
+
supported: every date gets its own Google Wallet class.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- pretix 2025.1 or newer, self-hosted
|
|
24
|
+
- A Google Wallet issuer account and a Google Cloud service account (see below)
|
|
25
|
+
- Your pretix installation must be reachable from the internet over HTTPS if you upload a logo or banner image,
|
|
26
|
+
because Google fetches the images from pretix.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Install the package into the Python environment of your pretix installation, then run the migrations and
|
|
31
|
+
restart pretix (web server and Celery workers):
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install pretix-google-wallet # or: pip install git+https://github.com/stblassitude/pretix-google-wallet
|
|
35
|
+
python -m pretix migrate
|
|
36
|
+
python -m pretix rebuild
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Setting up Google Wallet
|
|
40
|
+
|
|
41
|
+
1. **Issuer account.** Open the [Google Pay & Wallet Console](https://pay.google.com/business/console), create a
|
|
42
|
+
business profile and activate the *Google Wallet API*. Note the numeric **Issuer ID**.
|
|
43
|
+
2. **Service account.** In the [Google Cloud console](https://console.cloud.google.com/), create (or pick) a
|
|
44
|
+
project, enable the **Google Wallet API**, create a **service account** and add a **JSON key** for it.
|
|
45
|
+
Download the key file.
|
|
46
|
+
3. **Grant access.** In the Pay & Wallet Console, go to *Users* and invite the service account's e-mail
|
|
47
|
+
address (`…@….iam.gserviceaccount.com`) with *Developer* access.
|
|
48
|
+
4. **Test, then publish.** A new issuer account is in demo mode: only Google accounts added as test users in
|
|
49
|
+
the console can save passes, and passes show a "TEST ONLY" label. When everything works, request publishing
|
|
50
|
+
access in the console. Google reviews your account before passes become available to everyone.
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
Enable the plugin for an event (*Settings → Plugins → Output and export formats → Google Wallet*), then
|
|
55
|
+
configure it under *Settings → Tickets → Google Wallet*:
|
|
56
|
+
|
|
57
|
+
| Setting | Description |
|
|
58
|
+
|-----------------------|-------------------------------------------------------------------------------------|
|
|
59
|
+
| Issuer ID | The numeric issuer ID from the Pay & Wallet Console. |
|
|
60
|
+
| Service account key | The contents of the JSON key file. |
|
|
61
|
+
| Issuer name | Shown at the top of the pass. Defaults to the organizer name. |
|
|
62
|
+
| Logo | Square PNG/JPEG, shown as a circle. 660×660 px recommended. |
|
|
63
|
+
| Banner image | Wide PNG/JPEG on the front of the pass. 1032×336 px recommended. |
|
|
64
|
+
| Background color | Pass color, e.g. `#3b1c4a`. Defaults to the event's primary color. |
|
|
65
|
+
| Show ticket price | Adds the price to the pass. |
|
|
66
|
+
| Additional information| Free text (multi-language) shown in the pass details. |
|
|
67
|
+
|
|
68
|
+
### Installation-wide credentials
|
|
69
|
+
|
|
70
|
+
On a self-hosted installation you usually want one set of credentials for all events. Add them to
|
|
71
|
+
`pretix.cfg`; the event-level fields then become optional and override the global values only when filled in:
|
|
72
|
+
|
|
73
|
+
```ini
|
|
74
|
+
[googlewallet]
|
|
75
|
+
issuer_id=3388000000012345678
|
|
76
|
+
service_account_file=/etc/pretix/google-wallet-key.json
|
|
77
|
+
; Optional: prefix for class and object IDs, default "pretix". Set this if several
|
|
78
|
+
; pretix installations share one issuer account.
|
|
79
|
+
id_prefix=pretix
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
As with all pretix settings, you can use environment variables instead, e.g. `PRETIX_GOOGLEWALLET_ISSUER_ID`,
|
|
83
|
+
and `PRETIX_GOOGLEWALLET_SERVICE_ACCOUNT` (the JSON itself) or `FILE__PRETIX_GOOGLEWALLET_SERVICE_ACCOUNT`
|
|
84
|
+
(a file containing it). The key file must be readable by the user pretix runs as.
|
|
85
|
+
|
|
86
|
+
## How it works
|
|
87
|
+
|
|
88
|
+
When a customer clicks *Add to Google Wallet*, pretix (in a Celery worker) creates or updates the event's
|
|
89
|
+
*event ticket class* and the ticket's *event ticket object* through the Google Wallet REST API, then redirects
|
|
90
|
+
the customer to a signed `https://pay.google.com/gp/v/save/…` link referencing that object.
|
|
91
|
+
|
|
92
|
+
IDs are derived from the event and ticket, e.g. class `<issuer>.pretix-<organizer>-<event>[-<date id>]` and
|
|
93
|
+
object `<issuer>.pretix-<organizer>-<event>-<order code>-<position id>`. The plugin stores which tickets have
|
|
94
|
+
a Google Wallet object, and only those orders cause API calls when they change.
|
|
95
|
+
|
|
96
|
+
The plugin only depends on `requests` and `PyJWT`, which pretix already ships; the Google client libraries are
|
|
97
|
+
not needed.
|
|
98
|
+
|
|
99
|
+
## Limitations
|
|
100
|
+
|
|
101
|
+
- Only Google Wallet is supported. Samsung Wallet and other wallets use different APIs. For Apple Wallet,
|
|
102
|
+
use [pretix-passbook](https://github.com/pretix/pretix-passbook).
|
|
103
|
+
- One button per ticket; there is no "add all tickets of the order" button, and passes are not attached to
|
|
104
|
+
e-mails (pretix skips redirect-type outputs for attachments).
|
|
105
|
+
- The ticket preview in the backend is disabled, because it would create real objects in Google Wallet.
|
|
106
|
+
- Check-ins are not reflected on the pass.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
uv venv --python 3.12 .venv
|
|
112
|
+
uv pip install --python .venv/bin/python pretix -e '.[dev]'
|
|
113
|
+
.venv/bin/pytest
|
|
114
|
+
.venv/bin/flake8 pretix_google_wallet tests && .venv/bin/isort -c pretix_google_wallet tests
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The tests mock the Google APIs with `responses`. To update translations, run
|
|
118
|
+
`django-admin makemessages -l de --no-location --no-obsolete` inside `pretix_google_wallet/` with
|
|
119
|
+
`DJANGO_SETTINGS_MODULE=pretix.testutils.settings`.
|
|
120
|
+
|
|
121
|
+
Releases are built by GitHub Actions when a `v*` tag is pushed and published to PyPI via trusted publishing.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy
|
|
2
|
+
|
|
3
|
+
from . import __version__
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from pretix.base.plugins import PluginConfig
|
|
7
|
+
except ImportError:
|
|
8
|
+
raise RuntimeError("Please use pretix 2025.1 or above to run this plugin!")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PluginApp(PluginConfig):
|
|
12
|
+
default = True
|
|
13
|
+
name = "pretix_google_wallet"
|
|
14
|
+
verbose_name = "Google Wallet"
|
|
15
|
+
|
|
16
|
+
class PretixPluginMeta:
|
|
17
|
+
name = gettext_lazy("Google Wallet")
|
|
18
|
+
author = "Stefan Bethke"
|
|
19
|
+
description = gettext_lazy(
|
|
20
|
+
"Let your attendees add their tickets to Google Wallet. Passes are updated automatically when "
|
|
21
|
+
"orders change or are canceled."
|
|
22
|
+
)
|
|
23
|
+
visible = True
|
|
24
|
+
version = __version__
|
|
25
|
+
category = "FORMAT"
|
|
26
|
+
compatibility = "pretix>=2025.1.0"
|
|
27
|
+
settings_links = [
|
|
28
|
+
(
|
|
29
|
+
(gettext_lazy("Tickets"), gettext_lazy("Google Wallet")),
|
|
30
|
+
"control:event.settings.tickets",
|
|
31
|
+
{},
|
|
32
|
+
),
|
|
33
|
+
]
|
|
34
|
+
navigation_links = []
|
|
35
|
+
|
|
36
|
+
def ready(self):
|
|
37
|
+
from . import signals # NOQA
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minimal client for the Google Wallet REST API.
|
|
3
|
+
|
|
4
|
+
We deliberately only depend on ``PyJWT`` and ``requests`` (both already required by pretix)
|
|
5
|
+
instead of pulling in the google-auth and google-api-python-client stacks.
|
|
6
|
+
"""
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
from urllib.parse import quote
|
|
12
|
+
|
|
13
|
+
import jwt
|
|
14
|
+
import requests
|
|
15
|
+
from django.core.cache import cache
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
API_BASE = "https://walletobjects.googleapis.com/walletobjects/v1"
|
|
20
|
+
SCOPE = "https://www.googleapis.com/auth/wallet_object.issuer"
|
|
21
|
+
SAVE_URL = "https://pay.google.com/gp/v/save/"
|
|
22
|
+
DEFAULT_TOKEN_URI = "https://oauth2.googleapis.com/token"
|
|
23
|
+
TIMEOUT = 20
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WalletError(Exception):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ServiceAccount:
|
|
31
|
+
"""A Google Cloud service account, as described by the JSON key file downloaded from the console."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, info: dict):
|
|
34
|
+
if not isinstance(info, dict) or info.get("type", "service_account") != "service_account":
|
|
35
|
+
raise WalletError("The credentials are not a Google Cloud service account key.")
|
|
36
|
+
missing = [k for k in ("client_email", "private_key") if not info.get(k)]
|
|
37
|
+
if missing:
|
|
38
|
+
raise WalletError("The service account key is missing: {}".format(", ".join(missing)))
|
|
39
|
+
self.email = info["client_email"]
|
|
40
|
+
self.private_key = info["private_key"]
|
|
41
|
+
self.key_id = info.get("private_key_id")
|
|
42
|
+
self.token_uri = info.get("token_uri") or DEFAULT_TOKEN_URI
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_json(cls, data: str) -> "ServiceAccount":
|
|
46
|
+
try:
|
|
47
|
+
info = json.loads(data)
|
|
48
|
+
except (TypeError, ValueError) as e:
|
|
49
|
+
raise WalletError("The service account key is not valid JSON: {}".format(e))
|
|
50
|
+
return cls(info)
|
|
51
|
+
|
|
52
|
+
def sign(self, claims: dict) -> str:
|
|
53
|
+
headers = {"kid": self.key_id} if self.key_id else None
|
|
54
|
+
try:
|
|
55
|
+
return jwt.encode(claims, self.private_key, algorithm="RS256", headers=headers)
|
|
56
|
+
except (ValueError, TypeError, jwt.PyJWTError) as e:
|
|
57
|
+
raise WalletError("Could not sign with the service account's private key: {}".format(e))
|
|
58
|
+
|
|
59
|
+
def access_token(self) -> str:
|
|
60
|
+
cache_key = "pretix_google_wallet:token:" + hashlib.sha256(
|
|
61
|
+
"{}:{}".format(self.email, self.key_id).encode()
|
|
62
|
+
).hexdigest()
|
|
63
|
+
token = cache.get(cache_key)
|
|
64
|
+
if token:
|
|
65
|
+
return token
|
|
66
|
+
|
|
67
|
+
now = int(time.time())
|
|
68
|
+
assertion = self.sign({
|
|
69
|
+
"iss": self.email,
|
|
70
|
+
"scope": SCOPE,
|
|
71
|
+
"aud": self.token_uri,
|
|
72
|
+
"iat": now,
|
|
73
|
+
"exp": now + 3600,
|
|
74
|
+
})
|
|
75
|
+
try:
|
|
76
|
+
r = requests.post(self.token_uri, data={
|
|
77
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
78
|
+
"assertion": assertion,
|
|
79
|
+
}, timeout=TIMEOUT)
|
|
80
|
+
except requests.RequestException as e:
|
|
81
|
+
raise WalletError("Could not reach Google to authenticate: {}".format(e))
|
|
82
|
+
if r.status_code != 200:
|
|
83
|
+
raise WalletError("Google rejected the service account credentials (HTTP {}): {}".format(
|
|
84
|
+
r.status_code, _error_message(r)))
|
|
85
|
+
d = r.json()
|
|
86
|
+
token = d["access_token"]
|
|
87
|
+
cache.set(cache_key, token, max(int(d.get("expires_in", 3600)) - 300, 60))
|
|
88
|
+
return token
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _error_message(response) -> str:
|
|
92
|
+
try:
|
|
93
|
+
d = response.json()
|
|
94
|
+
except ValueError:
|
|
95
|
+
return response.text[:500]
|
|
96
|
+
err = d.get("error")
|
|
97
|
+
if isinstance(err, dict):
|
|
98
|
+
return err.get("message") or json.dumps(err)
|
|
99
|
+
return d.get("error_description") or str(err or d)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class WalletClient:
|
|
103
|
+
def __init__(self, account: ServiceAccount):
|
|
104
|
+
self.account = account
|
|
105
|
+
|
|
106
|
+
def _request(self, method: str, path: str, body: dict = None):
|
|
107
|
+
try:
|
|
108
|
+
return requests.request(
|
|
109
|
+
method, API_BASE + path, json=body, timeout=TIMEOUT,
|
|
110
|
+
headers={"Authorization": "Bearer " + self.account.access_token()},
|
|
111
|
+
)
|
|
112
|
+
except requests.RequestException as e:
|
|
113
|
+
raise WalletError("Could not reach the Google Wallet API: {}".format(e))
|
|
114
|
+
|
|
115
|
+
def get(self, kind: str, resource_id: str):
|
|
116
|
+
r = self._request("GET", "/{}/{}".format(kind, quote(resource_id, safe="")))
|
|
117
|
+
if r.status_code == 404:
|
|
118
|
+
return None
|
|
119
|
+
if r.status_code != 200:
|
|
120
|
+
raise WalletError("Could not fetch {} {} (HTTP {}): {}".format(
|
|
121
|
+
kind, resource_id, r.status_code, _error_message(r)))
|
|
122
|
+
return r.json()
|
|
123
|
+
|
|
124
|
+
def upsert(self, kind: str, resource: dict) -> dict:
|
|
125
|
+
"""
|
|
126
|
+
Create or fully replace a class or object. ``kind`` is e.g. ``eventTicketClass`` or
|
|
127
|
+
``eventTicketObject``.
|
|
128
|
+
"""
|
|
129
|
+
r = self._request("PUT", "/{}/{}".format(kind, quote(resource["id"], safe="")), resource)
|
|
130
|
+
if r.status_code == 404:
|
|
131
|
+
r = self._request("POST", "/{}".format(kind), resource)
|
|
132
|
+
if r.status_code not in (200, 201):
|
|
133
|
+
raise WalletError("Could not store {} {} (HTTP {}): {}".format(
|
|
134
|
+
kind, resource["id"], r.status_code, _error_message(r)))
|
|
135
|
+
return r.json()
|
|
136
|
+
|
|
137
|
+
def save_url(self, object_ids, origins=()) -> str:
|
|
138
|
+
"""
|
|
139
|
+
Build a "Save to Google Wallet" link for objects that already exist through the API.
|
|
140
|
+
"""
|
|
141
|
+
claims = {
|
|
142
|
+
"iss": self.account.email,
|
|
143
|
+
"aud": "google",
|
|
144
|
+
"typ": "savetowallet",
|
|
145
|
+
"iat": int(time.time()),
|
|
146
|
+
"origins": list(origins),
|
|
147
|
+
"payload": {
|
|
148
|
+
"eventTicketObjects": [{"id": i} for i in object_ids],
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
return SAVE_URL + self.account.sign(claims)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# German translation of pretix-google-wallet.
|
|
2
|
+
# This file is distributed under the same license as the pretix-google-wallet package.
|
|
3
|
+
#
|
|
4
|
+
msgid ""
|
|
5
|
+
msgstr ""
|
|
6
|
+
"Project-Id-Version: pretix-google-wallet\n"
|
|
7
|
+
"Report-Msgid-Bugs-To: \n"
|
|
8
|
+
"POT-Creation-Date: 2026-11-14 09:08+0000\n"
|
|
9
|
+
"PO-Revision-Date: 2026-11-14 10:15+0100\n"
|
|
10
|
+
"Last-Translator: Stefan Bethke <stb@lassitu.de>\n"
|
|
11
|
+
"Language-Team: German\n"
|
|
12
|
+
"Language: de\n"
|
|
13
|
+
"MIME-Version: 1.0\n"
|
|
14
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
15
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
16
|
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
17
|
+
|
|
18
|
+
msgid "Google Wallet"
|
|
19
|
+
msgstr "Google Wallet"
|
|
20
|
+
|
|
21
|
+
msgid ""
|
|
22
|
+
"Let your attendees add their tickets to Google Wallet. Passes are updated "
|
|
23
|
+
"automatically when orders change or are canceled."
|
|
24
|
+
msgstr ""
|
|
25
|
+
"Ermöglichen Sie Ihren Teilnehmenden, ihre Tickets zu Google Wallet "
|
|
26
|
+
"hinzuzufügen. Die Pässe werden automatisch aktualisiert, wenn Bestellungen "
|
|
27
|
+
"geändert oder storniert werden."
|
|
28
|
+
|
|
29
|
+
msgid "Tickets"
|
|
30
|
+
msgstr "Tickets"
|
|
31
|
+
|
|
32
|
+
msgid ""
|
|
33
|
+
"Attendees get a button that adds their ticket to Google Wallet. Passes stay "
|
|
34
|
+
"connected to pretix: when an order is changed or canceled, or the event date "
|
|
35
|
+
"or location changes, the pass in the wallet is updated."
|
|
36
|
+
msgstr ""
|
|
37
|
+
"Teilnehmende erhalten eine Schaltfläche, mit der sie ihr Ticket zu Google "
|
|
38
|
+
"Wallet hinzufügen können. Die Pässe bleiben mit pretix verbunden: Wird eine "
|
|
39
|
+
"Bestellung geändert oder storniert, oder ändern sich Datum oder Ort der "
|
|
40
|
+
"Veranstaltung, wird der Pass in der Wallet aktualisiert."
|
|
41
|
+
|
|
42
|
+
msgid ""
|
|
43
|
+
"You need an issuer account in the Google Pay & Wallet Console and a "
|
|
44
|
+
"Google Cloud service account that has been added as a user to that issuer "
|
|
45
|
+
"account. Until Google has approved your issuer account for publishing, only "
|
|
46
|
+
"test users added in the console can save passes."
|
|
47
|
+
msgstr ""
|
|
48
|
+
"Sie benötigen ein Ausstellerkonto in der Google Pay & Wallet Console und "
|
|
49
|
+
"ein Google-Cloud-Dienstkonto, das diesem Ausstellerkonto als Nutzer "
|
|
50
|
+
"hinzugefügt wurde. Bis Google Ihr Ausstellerkonto für die Veröffentlichung "
|
|
51
|
+
"freigegeben hat, können nur in der Console eingetragene Testnutzer Pässe "
|
|
52
|
+
"speichern."
|
|
53
|
+
|
|
54
|
+
msgid ""
|
|
55
|
+
"Images are loaded by Google from this pretix installation, so it needs to be "
|
|
56
|
+
"reachable from the internet via HTTPS."
|
|
57
|
+
msgstr ""
|
|
58
|
+
"Bilder werden von Google direkt von dieser pretix-Installation geladen, sie "
|
|
59
|
+
"muss daher aus dem Internet per HTTPS erreichbar sein."
|
|
60
|
+
|
|
61
|
+
msgid "Add to Google Wallet"
|
|
62
|
+
msgstr "Zu Google Wallet hinzufügen"
|
|
63
|
+
|
|
64
|
+
msgid "Leave empty to use the value configured in pretix.cfg."
|
|
65
|
+
msgstr "Leer lassen, um den Wert aus der pretix.cfg zu verwenden."
|
|
66
|
+
|
|
67
|
+
msgid "Issuer ID"
|
|
68
|
+
msgstr "Aussteller-ID"
|
|
69
|
+
|
|
70
|
+
msgid "The numeric issuer ID shown in the Google Pay & Wallet Console."
|
|
71
|
+
msgstr "Die numerische Aussteller-ID aus der Google Pay & Wallet Console."
|
|
72
|
+
|
|
73
|
+
msgid "The issuer ID must be numeric."
|
|
74
|
+
msgstr "Die Aussteller-ID muss numerisch sein."
|
|
75
|
+
|
|
76
|
+
msgid "Service account key"
|
|
77
|
+
msgstr "Dienstkonto-Schlüssel"
|
|
78
|
+
|
|
79
|
+
msgid ""
|
|
80
|
+
"The contents of the JSON key file of a Google Cloud service account that has "
|
|
81
|
+
"access to your issuer account."
|
|
82
|
+
msgstr ""
|
|
83
|
+
"Der Inhalt der JSON-Schlüsseldatei eines Google-Cloud-Dienstkontos, das "
|
|
84
|
+
"Zugriff auf Ihr Ausstellerkonto hat."
|
|
85
|
+
|
|
86
|
+
msgid "Issuer name"
|
|
87
|
+
msgstr "Name des Ausstellers"
|
|
88
|
+
|
|
89
|
+
msgid "Shown on the pass. Defaults to the name of the organizer."
|
|
90
|
+
msgstr "Wird auf dem Pass angezeigt. Standardmäßig der Name des Veranstalters."
|
|
91
|
+
|
|
92
|
+
msgid "Logo"
|
|
93
|
+
msgstr "Logo"
|
|
94
|
+
|
|
95
|
+
msgid ""
|
|
96
|
+
"Square image, shown as a circle at the top of the pass. Recommended size: "
|
|
97
|
+
"660×660 pixels."
|
|
98
|
+
msgstr ""
|
|
99
|
+
"Quadratisches Bild, wird oben auf dem Pass als Kreis angezeigt. Empfohlene "
|
|
100
|
+
"Größe: 660×660 Pixel."
|
|
101
|
+
|
|
102
|
+
msgid "Banner image"
|
|
103
|
+
msgstr "Bannerbild"
|
|
104
|
+
|
|
105
|
+
msgid ""
|
|
106
|
+
"Wide banner image shown on the front of the pass. Recommended size: 1032×336 "
|
|
107
|
+
"pixels."
|
|
108
|
+
msgstr ""
|
|
109
|
+
"Breites Bannerbild auf der Vorderseite des Passes. Empfohlene Größe: "
|
|
110
|
+
"1032×336 Pixel."
|
|
111
|
+
|
|
112
|
+
msgid "Background color"
|
|
113
|
+
msgstr "Hintergrundfarbe"
|
|
114
|
+
|
|
115
|
+
msgid "Defaults to the primary color of the event."
|
|
116
|
+
msgstr "Standardmäßig die Primärfarbe der Veranstaltung."
|
|
117
|
+
|
|
118
|
+
msgid "Please enter a color like #3b1c4a."
|
|
119
|
+
msgstr "Bitte geben Sie eine Farbe wie #3b1c4a ein."
|
|
120
|
+
|
|
121
|
+
msgid "Show ticket price on the pass"
|
|
122
|
+
msgstr "Ticketpreis auf dem Pass anzeigen"
|
|
123
|
+
|
|
124
|
+
msgid "Additional information"
|
|
125
|
+
msgstr "Zusätzliche Informationen"
|
|
126
|
+
|
|
127
|
+
msgid "Shown in the details of the pass."
|
|
128
|
+
msgstr "Wird in den Details des Passes angezeigt."
|
|
129
|
+
|
|
130
|
+
msgid ""
|
|
131
|
+
"Google Wallet is not configured: an issuer ID and service account key are "
|
|
132
|
+
"required."
|
|
133
|
+
msgstr ""
|
|
134
|
+
"Google Wallet ist nicht konfiguriert: Aussteller-ID und Dienstkonto-"
|
|
135
|
+
"Schlüssel werden benötigt."
|
|
136
|
+
|
|
137
|
+
msgid "The Google Wallet issuer ID must be numeric."
|
|
138
|
+
msgstr "Die Google-Wallet-Aussteller-ID muss numerisch sein."
|
|
139
|
+
|
|
140
|
+
msgid "Information"
|
|
141
|
+
msgstr "Informationen"
|
|
142
|
+
|
|
143
|
+
msgid "Ticket details"
|
|
144
|
+
msgstr "Ticketdetails"
|