pretix-thepay 9.0.18__py3-none-any.whl

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.
@@ -0,0 +1,10 @@
1
+ """
2
+ URL configuration for pretix_thepay plugin.
3
+ This file is automatically discovered by Pretix.
4
+ """
5
+ from django.urls import path, include
6
+
7
+ urlpatterns = [
8
+ path('thepay/', include('pretix_thepay.urls')),
9
+ ]
10
+
@@ -0,0 +1,18 @@
1
+ """
2
+ Signal handlers for The Pay payment provider registration.
3
+ """
4
+ from django.dispatch import receiver
5
+ from pretix.base.signals import register_payment_providers
6
+
7
+
8
+ @receiver(register_payment_providers, dispatch_uid="payment_thepay")
9
+ def register_payment_provider(sender, **kwargs):
10
+ """
11
+ Register The Pay payment provider with Pretix.
12
+
13
+ This signal handler is called by Pretix to discover available
14
+ payment providers and registers the The Pay provider.
15
+ """
16
+ from .payment import ThePay
17
+ return ThePay
18
+
pretix_thepay/urls.py ADDED
@@ -0,0 +1,15 @@
1
+ """
2
+ URL configuration for The Pay payment provider.
3
+
4
+ Defines routes for handling payment gateway callbacks:
5
+ - return: User redirect after payment completion
6
+ - notify: Server-to-server notification (IPN)
7
+ """
8
+ from django.urls import path
9
+ from . import views
10
+
11
+ urlpatterns = [
12
+ path('return/<str:order>/<int:payment>/<str:hash>/', views.ReturnView.as_view(), name='return'),
13
+ path('notify/<str:order>/<int:payment>/<str:hash>/', views.NotifyView.as_view(), name='notify'),
14
+ ]
15
+
pretix_thepay/views.py ADDED
@@ -0,0 +1,345 @@
1
+ """
2
+ Views for handling The Pay payment gateway callbacks.
3
+
4
+ This module provides views for processing user redirects and server-to-server
5
+ notifications from the The Pay payment gateway, including signature verification
6
+ and payment status updates.
7
+ """
8
+ import logging
9
+ from django.contrib import messages
10
+ from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest
11
+ from django.shortcuts import redirect, get_object_or_404
12
+ from django.urls import reverse, NoReverseMatch
13
+ from django.utils.decorators import method_decorator
14
+ from django.utils.translation import gettext_lazy as _
15
+ from django.views import View
16
+ from django.views.decorators.csrf import csrf_exempt
17
+ from django_scopes import scopes_disabled
18
+ from pretix.base.models import OrderPayment
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def _order_redirect_url(request, order) -> str:
24
+ if hasattr(order, 'get_absolute_url'):
25
+ return order.get_absolute_url()
26
+ if hasattr(order, 'get_detail_url'):
27
+ return order.get_detail_url()
28
+ if hasattr(order, 'get_url'):
29
+ return order.get_url()
30
+ if hasattr(order, 'get_presale_url'):
31
+ return order.get_presale_url()
32
+ try:
33
+ patterns = [
34
+ ('presale:event.order', {
35
+ 'organizer': order.event.organizer.slug,
36
+ 'event': order.event.slug,
37
+ 'code': order.code,
38
+ 'secret': order.secret,
39
+ }),
40
+ ('presale:event.order', {
41
+ 'event': order.event.slug,
42
+ 'code': order.code,
43
+ 'secret': order.secret,
44
+ }),
45
+ ('presale:event.order.by_code', {
46
+ 'organizer': order.event.organizer.slug,
47
+ 'event': order.event.slug,
48
+ 'code': order.code,
49
+ 'secret': order.secret,
50
+ }),
51
+ ('presale:event.order.by_code', {
52
+ 'event': order.event.slug,
53
+ 'code': order.code,
54
+ 'secret': order.secret,
55
+ }),
56
+ ]
57
+ for name, kwargs in patterns:
58
+ try:
59
+ return reverse(name, kwargs=kwargs)
60
+ except NoReverseMatch:
61
+ continue
62
+ except Exception:
63
+ pass
64
+ try:
65
+ organizer = order.event.organizer.slug
66
+ event = order.event.slug
67
+ code = order.code
68
+ secret = order.secret
69
+ path = f'/{organizer}/{event}/order/{code}/{secret}/'
70
+ return request.build_absolute_uri(path)
71
+ except Exception:
72
+ return '/'
73
+
74
+
75
+ def _store_thepay_state(payment_obj: OrderPayment, state: str) -> None:
76
+ info = getattr(payment_obj, 'info_data', None)
77
+ if info is None:
78
+ info = getattr(payment_obj, 'info', None) or {}
79
+ info['thepay_state'] = state
80
+ if hasattr(payment_obj, 'info_data'):
81
+ payment_obj.info_data = info
82
+ else:
83
+ payment_obj.info = info
84
+ payment_obj.save(update_fields=['info'])
85
+
86
+
87
+ @method_decorator(csrf_exempt, name='dispatch')
88
+ class ReturnView(View):
89
+ """
90
+ Handle user redirect return from The Pay payment gateway.
91
+
92
+ Processes the payment response when the customer is redirected back
93
+ from the The Pay gateway after completing or canceling payment.
94
+ """
95
+
96
+ def get(self, request: HttpRequest, order: str, payment: int, hash: str):
97
+ return self._handle_response(request, order, payment, hash)
98
+
99
+ def post(self, request: HttpRequest, order: str, payment: int, hash: str):
100
+ return self._handle_response(request, order, payment, hash)
101
+
102
+ def _handle_response(self, request: HttpRequest, order: str, payment: int, hash: str):
103
+ """
104
+ Process The Pay return response.
105
+
106
+ Verifies the payment signature, checks payment status,
107
+ and updates the payment state accordingly.
108
+
109
+ Args:
110
+ request: HTTP request containing The Pay response parameters
111
+ order: Order code
112
+ payment: Payment ID
113
+ hash: Order secret hash for validation
114
+ """
115
+ try:
116
+ with scopes_disabled():
117
+ payment_obj = get_object_or_404(
118
+ OrderPayment,
119
+ id=payment,
120
+ order__code=order,
121
+ order__secret=hash
122
+ )
123
+ order_obj = payment_obj.order
124
+ event = order_obj.event
125
+
126
+ # Get payment provider
127
+ from pretix.base.models import Event
128
+ provider = event.get_payment_providers().get('thepay')
129
+ if not provider:
130
+ logger.error('The Pay provider not found')
131
+ messages.error(request, _('Payment provider not configured.'))
132
+ return redirect(order_obj.get_abandon_url())
133
+
134
+ # Get settings
135
+ settings_dict = provider.settings
136
+ api_password = settings_dict.get('api_password', '')
137
+
138
+ # Get response parameters
139
+ # The Pay return URL includes payment_uid and project_id
140
+ payment_uid = (
141
+ request.GET.get('payment_uid', '') or request.POST.get('payment_uid', '') or
142
+ request.GET.get('uid', '') or request.POST.get('uid', '')
143
+ )
144
+ signature = request.GET.get('signature', '') or request.POST.get('signature', '')
145
+
146
+ # If no payment_uid from callback, extract from payment.info (stored during creation)
147
+ if not payment_uid:
148
+ payment_info = getattr(payment_obj, 'info_data', None)
149
+ if payment_info is None:
150
+ payment_info = getattr(payment_obj, 'info', None) or {}
151
+ payment_uid = payment_info.get('payment_uid') or payment_info.get('uid')
152
+ # Fallback: reconstruct from stored pattern
153
+ if not payment_uid:
154
+ payment_uid = f'pretix-{payment_obj.id}-{order_obj.code}'
155
+
156
+ # Verify signature if provided
157
+ if signature and api_password:
158
+ params = dict(request.GET.items()) if request.method == 'GET' else dict(request.POST.items())
159
+ params.pop('signature', None) # Remove signature from params for verification
160
+
161
+ if not provider._verify_signature(params, signature, api_password):
162
+ logger.error('The Pay signature verification failed')
163
+ messages.error(request, _('Payment verification failed.'))
164
+ return redirect(order_obj.get_abandon_url())
165
+
166
+ # Always query payment status via API (per The Pay spec)
167
+ payment_state = None
168
+ if payment_uid:
169
+ merchant_id = settings_dict.get('merchant_id', '')
170
+ project_id = settings_dict.get('project_id', '')
171
+ test_mode = settings_dict.get('test_mode', False)
172
+ api_url = provider._get_api_url(test_mode).rstrip('/')
173
+ payment_state = provider._get_payment_status(
174
+ payment_uid, merchant_id, project_id, api_password, api_url
175
+ )
176
+ logger.info('The Pay return status for payment %s (uid=%s): %s', payment, payment_uid, payment_state)
177
+
178
+ # Check payment status
179
+ # The Pay payment states: 'paid', 'waiting_for_payment', 'expired', 'error', etc.
180
+ # According to OpenAPI spec: Enum_PaymentState
181
+ if payment_state:
182
+ _store_thepay_state(payment_obj, payment_state)
183
+ if payment_state in ('paid', 'refunded', 'partially_refunded'):
184
+ allowed_states = {OrderPayment.PAYMENT_STATE_PENDING}
185
+ created_state = getattr(OrderPayment, 'PAYMENT_STATE_CREATED', None)
186
+ if created_state:
187
+ allowed_states.add(created_state)
188
+ if payment_obj.state in allowed_states:
189
+ logger.info('The Pay payment %s state before confirm: %s', payment, payment_obj.state)
190
+ with scopes_disabled():
191
+ payment_obj.confirm()
192
+ logger.info('The Pay payment %s confirmed for order %s', payment, order)
193
+ if payment_state == 'paid':
194
+ messages.success(request, _('Payment successful!'))
195
+ else:
196
+ messages.warning(request, _('Payment was refunded on The Pay side.'))
197
+ else:
198
+ logger.info('The Pay payment %s already in state %s; skipping confirm', payment, payment_obj.state)
199
+ return redirect(_order_redirect_url(request, order_obj))
200
+ elif payment_state in ('expired', 'error', 'preauth_cancelled', 'preauth_expired'):
201
+ error_msg = _('Payment failed or expired.')
202
+ if payment_obj.state == OrderPayment.PAYMENT_STATE_PENDING:
203
+ with scopes_disabled():
204
+ payment_obj.fail(info={'error': f'Payment state: {payment_state}'})
205
+ logger.warning(f'The Pay payment {payment} failed for order {order}: {payment_state}')
206
+ messages.error(request, error_msg)
207
+ return redirect(order_obj.get_abandon_url())
208
+ elif payment_state in ('waiting_for_payment', 'waiting_for_confirmation', 'preauthorized'):
209
+ # Payment is still processing
210
+ logger.info(f'The Pay payment {payment} is still processing: {payment_state}')
211
+ return redirect(_order_redirect_url(request, order_obj))
212
+ else:
213
+ # Unknown status or no status - keep pending
214
+ if payment_state:
215
+ logger.warning(f'The Pay payment {payment} returned unknown status: {payment_state}')
216
+ else:
217
+ logger.warning(f'The Pay payment {payment} - no status in callback and API query failed')
218
+ return redirect(_order_redirect_url(request, order_obj))
219
+
220
+ except Exception as e:
221
+ logger.error(f'Error processing The Pay return: {e}', exc_info=True)
222
+ messages.error(request, _('An error occurred while processing your payment.'))
223
+ try:
224
+ return redirect(order_obj.get_abandon_url())
225
+ except:
226
+ return HttpResponseBadRequest('Error processing payment')
227
+
228
+
229
+ @method_decorator(csrf_exempt, name='dispatch')
230
+ class NotifyView(View):
231
+ """
232
+ Handle server-to-server notification (IPN) from The Pay payment gateway.
233
+
234
+ Processes asynchronous payment notifications sent by The Pay to confirm
235
+ payment status independently of user redirect.
236
+ """
237
+
238
+ def get(self, request: HttpRequest, order: str, payment: int, hash: str):
239
+ return self._handle_notification(request, order, payment, hash)
240
+
241
+ def post(self, request: HttpRequest, order: str, payment: int, hash: str):
242
+ return self._handle_notification(request, order, payment, hash)
243
+
244
+ def _handle_notification(self, request: HttpRequest, order: str, payment: int, hash: str):
245
+ """
246
+ Process The Pay server notification.
247
+
248
+ Verifies the notification signature, checks payment status,
249
+ and updates the payment state. Returns HTTP 200 OK on success.
250
+
251
+ Args:
252
+ request: HTTP request containing The Pay notification parameters
253
+ order: Order code
254
+ payment: Payment ID
255
+ hash: Order secret hash for validation
256
+ """
257
+ try:
258
+ with scopes_disabled():
259
+ payment_obj = get_object_or_404(
260
+ OrderPayment,
261
+ id=payment,
262
+ order__code=order,
263
+ order__secret=hash
264
+ )
265
+ order_obj = payment_obj.order
266
+ event = order_obj.event
267
+
268
+ # Get payment provider
269
+ from pretix.base.models import Event
270
+ provider = event.get_payment_providers().get('thepay')
271
+ if not provider:
272
+ logger.error('The Pay provider not found')
273
+ return HttpResponseBadRequest('Provider not configured')
274
+
275
+ # Get settings
276
+ settings_dict = provider.settings
277
+ api_password = settings_dict.get('api_password', '')
278
+
279
+ # Get notification parameters
280
+ # The Pay notifications include: payment_uid, project_id, type
281
+ payment_uid = (
282
+ request.GET.get('payment_uid', '') or request.POST.get('payment_uid', '') or
283
+ request.GET.get('uid', '') or request.POST.get('uid', '')
284
+ )
285
+ signature = request.GET.get('signature', '') or request.POST.get('signature', '')
286
+
287
+ # If no payment_uid from notification, extract from payment.info (stored during creation)
288
+ if not payment_uid:
289
+ payment_info = getattr(payment_obj, 'info_data', None)
290
+ if payment_info is None:
291
+ payment_info = getattr(payment_obj, 'info', None) or {}
292
+ payment_uid = payment_info.get('payment_uid') or payment_info.get('uid')
293
+ # Fallback: reconstruct from stored pattern
294
+ if not payment_uid:
295
+ payment_uid = f'pretix-{payment_obj.id}-{order_obj.code}'
296
+
297
+ # Verify signature if provided
298
+ if signature and api_password:
299
+ params = dict(request.GET.items()) if request.method == 'GET' else dict(request.POST.items())
300
+ params.pop('signature', None)
301
+
302
+ if not provider._verify_signature(params, signature, api_password):
303
+ logger.error('The Pay notification signature verification failed')
304
+ return HttpResponseBadRequest('Invalid signature')
305
+
306
+ # Always query payment status via API (per The Pay spec)
307
+ payment_state = None
308
+ if payment_uid:
309
+ merchant_id = settings_dict.get('merchant_id', '')
310
+ project_id = settings_dict.get('project_id', '')
311
+ test_mode = settings_dict.get('test_mode', False)
312
+ api_url = provider._get_api_url(test_mode).rstrip('/')
313
+ payment_state = provider._get_payment_status(
314
+ payment_uid, merchant_id, project_id, api_password, api_url
315
+ )
316
+ logger.info('The Pay notify status for payment %s (uid=%s): %s', payment, payment_uid, payment_state)
317
+
318
+ # Process payment status
319
+ # The Pay payment states: 'paid', 'waiting_for_payment', 'expired', 'error', etc.
320
+ # Notification type 'state_changed' indicates payment state has changed
321
+ if payment_state:
322
+ _store_thepay_state(payment_obj, payment_state)
323
+ if payment_state in ('paid', 'refunded', 'partially_refunded'):
324
+ allowed_states = {OrderPayment.PAYMENT_STATE_PENDING}
325
+ created_state = getattr(OrderPayment, 'PAYMENT_STATE_CREATED', None)
326
+ if created_state:
327
+ allowed_states.add(created_state)
328
+ if payment_obj.state in allowed_states:
329
+ logger.info('The Pay payment %s state before confirm (notify): %s', payment, payment_obj.state)
330
+ with scopes_disabled():
331
+ payment_obj.confirm()
332
+ logger.info('The Pay payment %s confirmed via notification for order %s', payment, order)
333
+ else:
334
+ logger.info('The Pay payment %s already in state %s; skipping confirm', payment, payment_obj.state)
335
+ elif payment_state in ('expired', 'error', 'preauth_cancelled', 'preauth_expired'):
336
+ if payment_obj.state == OrderPayment.PAYMENT_STATE_PENDING:
337
+ with scopes_disabled():
338
+ payment_obj.fail(info={'error': f'Payment state: {payment_state}'})
339
+ logger.warning(f'The Pay payment {payment} failed via notification for order {order}: {payment_state}')
340
+
341
+ return HttpResponse('OK')
342
+
343
+ except Exception as e:
344
+ logger.error(f'Error processing The Pay notification: {e}', exc_info=True)
345
+ return HttpResponseBadRequest('Error processing notification')
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.1
2
+ Name: pretix-thepay
3
+ Version: 9.0.18
4
+ Summary: The Pay payment provider for Pretix
5
+ Author-email: KrisIsNew <thepay.pretix@krisisnew.xyz>
6
+ Maintainer-email: KrisIsNew <thepay.pretix@krisisnew.xyz>
7
+ License: Apache-2.0
8
+ Project-URL: homepage, https://github.com/krisisnewxyz/pretix-thepay
9
+ Project-URL: repository, https://github.com/krisisnewxyz/pretix-thepay
10
+ Project-URL: documentation, https://github.com/krisisnewxyz/pretix-thepay
11
+ Project-URL: issues, https://github.com/krisisnewxyz/pretix-thepay/issues
12
+ Keywords: pretix,payment,thepay
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/x-rst
24
+ Requires-Dist: pretix >=2.7.0
25
+ Requires-Dist: requests >=2.25.0
26
+
27
+ pretix-thepay
28
+ =============
29
+
30
+ The Pay payment provider plugin for Pretix. It creates payments via The Pay API,
31
+ redirects customers to The Pay, and confirms payment status using the return and
32
+ notification callbacks.
33
+
34
+ Features
35
+ --------
36
+
37
+ - The Pay REST API integration (payment creation, status lookup)
38
+ - Redirect-based checkout flow
39
+ - Server-to-server notifications
40
+ - Refunds (full and partial)
41
+ - ISO 4217 currency support with correct minor units
42
+ - Demo mode via Test Mode setting
43
+ - Multi-language gateway support (Czech, Slovak, English)
44
+
45
+ Requirements
46
+ ------------
47
+
48
+ - Pretix >= 2.7.0
49
+ - Python >= 3.8
50
+ - requests >= 2.25.0
51
+
52
+ Installation
53
+ ------------
54
+
55
+ Install from PyPI::
56
+
57
+ pip install pretix-thepay
58
+
59
+ Or install from source::
60
+
61
+ pip install -e /path/to/pretix-thepay
62
+
63
+ Enable the plugin in ``pretix.cfg``::
64
+
65
+ [pretix]
66
+ plugins = pretix_thepay
67
+
68
+ Restart Pretix after enabling the plugin.
69
+
70
+ Configuration
71
+ -------------
72
+
73
+ In the Pretix control panel, enable The Pay and configure:
74
+
75
+ - **Merchant ID**: Your The Pay merchant ID
76
+ - **Project ID**: Your The Pay project ID
77
+ - **API Password**: Your The Pay API password
78
+ - **Language**: Default language for the payment gateway
79
+ - **Test Mode**: Enable to use the demo environment
80
+
81
+ Behavior
82
+ --------
83
+
84
+ 1. The customer selects The Pay.
85
+ 2. Pretix creates a payment at The Pay and redirects the customer.
86
+ 3. The customer completes payment on The Pay.
87
+ 4. Pretix confirms the payment by querying The Pay from the return URL and from
88
+ notifications.
89
+
90
+ Notes
91
+ -----
92
+
93
+ - Test Mode switches the API base URL to the demo environment.
94
+ - The Pay requires a customer name plus email or phone; Pretix order data is used.
95
+
96
+ Refunds
97
+ -------
98
+
99
+ Refunds are initiated from Pretix and sent to The Pay. The plugin supports full
100
+ and partial refunds for payments that support automatic refunds in The Pay.
101
+
102
+ Troubleshooting
103
+ ---------------
104
+
105
+ Payment not created
106
+ ^^^^^^^^^^^^^^^^^^^
107
+
108
+ - Verify Merchant ID, Project ID, and API Password
109
+ - Check Pretix logs for The Pay API response details
110
+ - Ensure the customer has a name and email or phone
111
+
112
+ Notifications not arriving
113
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
114
+
115
+ - Make sure Pretix is reachable from The Pay (public URL)
116
+ - Confirm your server IP is allowed in The Pay administration
117
+
118
+ Docker
119
+ ------
120
+
121
+ For Pretix in Docker::
122
+
123
+ FROM pretix/standalone:stable
124
+ USER root
125
+ RUN pip3 install pretix-thepay
126
+ USER pretixuser
127
+ RUN cd /pretix/src && make production
128
+
129
+ License
130
+ -------
131
+
132
+ Apache Software License 2.0
133
+
134
+ References
135
+ ----------
136
+
137
+ - `Pretix Plugin Development Guide <https://docs.pretix.eu/dev/development/api/index.html>`_
138
+ - `The Pay API Documentation <https://docs.thepay.eu/>`_
139
+ - `The Pay OpenAPI Spec <https://gate.thepay.cz/openapi.yaml>`_
@@ -0,0 +1,13 @@
1
+ pretix_thepay/__init__.py,sha256=WM1ftBLz5HhVzbLgstZ_85ohCNYu5BlUZCwSETxLWhc,449
2
+ pretix_thepay/apps.py,sha256=NuyUCL3dccqPU0DbGu4yS7JIey6kZ5-BXyYLvzDoNA0,1114
3
+ pretix_thepay/payment.py,sha256=nmQyk5-ZB6_mHAAzcgnfoAUpCVMbg5IrudSXgtP8gtQ,31717
4
+ pretix_thepay/pretix_plugin_urls.py,sha256=RDrQfsuBA326V7WBBKL6Q27aLCgXnGMINXaRYp6dO6U,211
5
+ pretix_thepay/signals.py,sha256=t0w6D5DPv7cZPbewgol7IMl7DKtMGF30q4i-0wwqlB0,530
6
+ pretix_thepay/urls.py,sha256=tG2vjSBIXtb4J0GuWrmjuC2NvXpvu3_5xf6kwZ9bHYI,478
7
+ pretix_thepay/views.py,sha256=lgeC5FPjDOtdqm8UtxqHmKbx7e3mu2kRlpUXMMa1rvQ,16159
8
+ pretix_thepay/locale/en/LC_MESSAGES/django.po,sha256=c2IiguXpHotpr3uBlDJR2GiVauyTpHMCFNHOqNMIn_M,104
9
+ pretix_thepay-9.0.18.dist-info/METADATA,sha256=vNqYJdlNc4vjv5a-QOSJz36OZ4KAeAQ77LthvaCd-OY,3828
10
+ pretix_thepay-9.0.18.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
11
+ pretix_thepay-9.0.18.dist-info/entry_points.txt,sha256=dlsZ6P-h88TN5VvfOmOGHQ-98-KCFjAdCi146Br5M-E,63
12
+ pretix_thepay-9.0.18.dist-info/top_level.txt,sha256=n1Ysl4WizfC1AMjn6xkNQ9vh7T7OR-1z8ZEnNc_79mg,14
13
+ pretix_thepay-9.0.18.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.42.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [pretix.plugin]
2
+ pretix_thepay = pretix_thepay:PretixPluginMeta
@@ -0,0 +1 @@
1
+ pretix_thepay