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.
Files changed (26) hide show
  1. pretix_google_wallet-0.9.0/LICENSE +21 -0
  2. pretix_google_wallet-0.9.0/MANIFEST.in +4 -0
  3. pretix_google_wallet-0.9.0/PKG-INFO +154 -0
  4. pretix_google_wallet-0.9.0/README.md +125 -0
  5. pretix_google_wallet-0.9.0/pretix_google_wallet/__init__.py +7 -0
  6. pretix_google_wallet-0.9.0/pretix_google_wallet/apps.py +37 -0
  7. pretix_google_wallet-0.9.0/pretix_google_wallet/client.py +151 -0
  8. pretix_google_wallet-0.9.0/pretix_google_wallet/locale/de/LC_MESSAGES/django.po +144 -0
  9. pretix_google_wallet-0.9.0/pretix_google_wallet/migrations/0001_initial.py +40 -0
  10. pretix_google_wallet-0.9.0/pretix_google_wallet/migrations/__init__.py +0 -0
  11. pretix_google_wallet-0.9.0/pretix_google_wallet/models.py +22 -0
  12. pretix_google_wallet-0.9.0/pretix_google_wallet/signals.py +62 -0
  13. pretix_google_wallet-0.9.0/pretix_google_wallet/tasks.py +59 -0
  14. pretix_google_wallet-0.9.0/pretix_google_wallet/templates/pretix_google_wallet/settings.html +20 -0
  15. pretix_google_wallet-0.9.0/pretix_google_wallet/ticketoutput.py +115 -0
  16. pretix_google_wallet-0.9.0/pretix_google_wallet/wallet.py +353 -0
  17. pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/PKG-INFO +154 -0
  18. pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/SOURCES.txt +25 -0
  19. pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/dependency_links.txt +1 -0
  20. pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/entry_points.txt +2 -0
  21. pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/requires.txt +9 -0
  22. pretix_google_wallet-0.9.0/pretix_google_wallet.egg-info/top_level.txt +1 -0
  23. pretix_google_wallet-0.9.0/pyproject.toml +70 -0
  24. pretix_google_wallet-0.9.0/setup.cfg +9 -0
  25. pretix_google_wallet-0.9.0/setup.py +24 -0
  26. 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,4 @@
1
+ recursive-include pretix_google_wallet/templates *.html
2
+ recursive-include pretix_google_wallet/locale *.po
3
+ include LICENSE
4
+ include README.md
@@ -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,7 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ # Set from the git tag by the release workflow, see pyproject.toml.
5
+ __version__ = version("pretix-google-wallet")
6
+ except PackageNotFoundError:
7
+ __version__ = "0.0.0.dev0"
@@ -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 &amp; 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 &amp; 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"