mirror of
https://github.com/pretix/pretix.git
synced 2026-09-04 14:44:42 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7da5d2e0e7 | ||
|
|
4390403b9a | ||
|
|
9d53cf840b | ||
|
|
023f9104ef | ||
|
|
e6572344ca |
@@ -123,7 +123,24 @@ jobs:
|
||||
working-directory: ./src
|
||||
run: make all compress
|
||||
- name: Install Playwright browsers
|
||||
run: playwright install
|
||||
run: playwright install --with-deps
|
||||
- name: Run E2E tests
|
||||
working-directory: ./src
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_postgres.cfg py.test tests/e2e/ -v --maxfail=10
|
||||
run: PRETIX_CONFIG_FILE=tests/ci_postgres.cfg py.test tests/e2e/ -v --maxfail=10 --tracing=retain-on-failure
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-traces
|
||||
path: test-results/
|
||||
- name: Log trace instructions
|
||||
if: steps.check-traces.outputs.found == 'true'
|
||||
run: |
|
||||
{
|
||||
echo "## 🎭 Playwright traces available"
|
||||
echo ""
|
||||
echo "Some tests failed or retried and produced traces."
|
||||
echo ""
|
||||
echo "1. Download the **playwright-traces-${{ github.run_id }}** artifact from this run (link in the **Summary** tab, under Artifacts)."
|
||||
echo "2. Unzip it."
|
||||
echo "3. Go to https://trace.playwright.dev and drag \`trace.zip\` into the page — or run \`npx playwright show-trace trace.zip\` locally."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
@@ -135,6 +135,8 @@ class BaseQuestionsViewMixin:
|
||||
question_field.initial = getattr(question_field, 'initial', None) or src['initial']
|
||||
if 'validators' in src:
|
||||
question_field.validators += src['validators']
|
||||
if 'label' in src:
|
||||
question_field.label = src['label']
|
||||
|
||||
if len(form.fields) > 0:
|
||||
formlist.append(form)
|
||||
|
||||
@@ -494,6 +494,7 @@ def webhook(request, *args, **kwargs):
|
||||
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
|
||||
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED):
|
||||
if sale['status'] == 'COMPLETED':
|
||||
any_captures = False
|
||||
all_captures_completed = True
|
||||
any_pending_review = False
|
||||
any_failed = None
|
||||
@@ -505,6 +506,7 @@ def webhook(request, *args, **kwargs):
|
||||
except ReferencedPayPalObject.MultipleObjectsReturned:
|
||||
pass
|
||||
|
||||
any_captures = True
|
||||
if capture['status'] in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
|
||||
pass
|
||||
elif capture['status'] in ("DECLINED", "FAILED"):
|
||||
@@ -516,7 +518,7 @@ def webhook(request, *args, **kwargs):
|
||||
any_pending_review = True
|
||||
else:
|
||||
raise ValueError("Unknown paypal capture state: {}".format(capture['status']))
|
||||
if all_captures_completed:
|
||||
if any_captures and all_captures_completed:
|
||||
try:
|
||||
payment.confirm()
|
||||
prov.log_payment_duration(payment)
|
||||
|
||||
@@ -840,6 +840,8 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
f.fields[fname].disabled = val['disabled']
|
||||
if 'validators' in val and fname in f.fields:
|
||||
f.fields[fname].validators += val['validators']
|
||||
if 'label' in val and fname in f.fields:
|
||||
f.fields[fname].label = val['label']
|
||||
|
||||
return f
|
||||
|
||||
@@ -944,6 +946,8 @@ class QuestionsStep(CartQuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
f.fields[fname].disabled = val['disabled']
|
||||
if 'validators' in val and fname in f.fields:
|
||||
f.fields[fname].validators += val['validators']
|
||||
if 'label' in val and fname in f.fields:
|
||||
f.fields[fname].label = val['label']
|
||||
|
||||
return f
|
||||
|
||||
|
||||
@@ -334,7 +334,7 @@ class ResetPasswordForm(forms.Form):
|
||||
def clean_email(self):
|
||||
if 'email' not in self.cleaned_data:
|
||||
return
|
||||
if rate_limit("customer_pwreset_check", max_num=10, expire_time=600):
|
||||
if rate_limit("customer_pwreset_check", include_ip_from_request=self.request, max_num=10, expire_time=600):
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['rate_limit'],
|
||||
code='rate_limit',
|
||||
|
||||
@@ -233,7 +233,7 @@ Arguments: ``request``, ``order``
|
||||
This signal allows you to override fields of the contact form that is presented during checkout
|
||||
and by default only asks for the email address. It is also being used for the invoice address
|
||||
form. You are supposed to return a dictionary of dictionaries with globally unique keys. The
|
||||
value-dictionary should contain one or more of the following keys: ``initial``, ``disabled``,
|
||||
value-dictionary should contain one or more of the following keys: ``label``, ``initial``, ``disabled``,
|
||||
``validators``. The key of the dictionary should be the name of the form field.
|
||||
|
||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event. A ``request``
|
||||
@@ -264,9 +264,9 @@ Arguments: ``position``, ``request``
|
||||
This signal allows you to override fields of the questions form that is presented during checkout
|
||||
and by default only asks for the questions configured in the backend. You are supposed to return a
|
||||
dictionary of dictionaries with globally unique keys. The value-dictionary should contain one or
|
||||
more of the following keys: ``initial``, ``disabled``, ``validators``. The key of the dictionary
|
||||
should be the form field name for system fields (e.g. ``company``), or the question's ``identifier``
|
||||
for user-defined questions.
|
||||
more of the following keys: ``label``, ``initial``, ``disabled``, ``validators``. The key of the
|
||||
dictionary should be the form field name for system fields (e.g. ``company``), or the question's
|
||||
``identifier`` for user-defined questions.
|
||||
|
||||
The ``position`` keyword argument will contain a ``CartPosition`` or ``OrderPosition`` object.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ import isoweek
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
from django.db.models import (
|
||||
Case, Exists, F, Max, Min, OuterRef, Prefetch, Q, Subquery, Value, When,
|
||||
Case, Exists, F, Max, Min, OuterRef, Prefetch, Q, Value, When,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.dispatch.dispatcher import NO_RECEIVERS
|
||||
@@ -189,27 +189,22 @@ class EventListMixin:
|
||||
def _get_event_list_queryset(self):
|
||||
query = Q(is_public=True) & Q(live=True)
|
||||
qs = self.request.organizer.events.using(settings.DATABASE_REPLICA).filter(query)
|
||||
qs = qs.filter(Q(all_sales_channels=True) | Q(id__in=self.request.sales_channel.event_set.values_list("pk")))
|
||||
qs = qs.filter(Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel))
|
||||
|
||||
show_old = "old" in self.request.GET
|
||||
|
||||
subevent_filter = Q(active=True, is_public=True)
|
||||
subevent_filter = Q(subevents__active=True, subevents__is_public=True)
|
||||
if not show_old:
|
||||
subevent_filter &= Q(
|
||||
Q(date_to__gte=now()) | Q(date_from__gte=now())
|
||||
Q(subevents__date_to__gte=now()) | Q(subevents__date_from__gte=now())
|
||||
)
|
||||
|
||||
subevent_subquery_qs = SubEvent.objects.with_scopes_disabled().filter(
|
||||
subevent_filter,
|
||||
event_id=OuterRef('pk')
|
||||
).values('event').order_by()
|
||||
qs = qs.annotate(
|
||||
min_from=Subquery(subevent_subquery_qs.annotate(m=Min('date_from')).values('m')),
|
||||
min_to=Subquery(subevent_subquery_qs.annotate(m=Min('date_to')).values('m')),
|
||||
max_from=Subquery(subevent_subquery_qs.annotate(m=Max('date_from')).values('m')),
|
||||
max_to=Subquery(subevent_subquery_qs.annotate(m=Max('date_to')).values('m')),
|
||||
).annotate(
|
||||
max_fromto=Greatest(F("max_to"), F("max_from")),
|
||||
min_from=Min('subevents__date_from', filter=subevent_filter),
|
||||
min_to=Min('subevents__date_to', filter=subevent_filter),
|
||||
max_from=Max('subevents__date_from', filter=subevent_filter),
|
||||
max_to=Max('subevents__date_to', filter=subevent_filter),
|
||||
max_fromto=Greatest(Max('subevents__date_to', filter=subevent_filter), Max('subevents__date_from', filter=subevent_filter)),
|
||||
)
|
||||
if show_old:
|
||||
date_q = Q(date_to__lt=now()) | (Q(date_to__isnull=True) & Q(date_from__lt=now()))
|
||||
|
||||
@@ -299,6 +299,30 @@ def get_test_order_review_pending():
|
||||
'method': 'GET'}]}
|
||||
|
||||
|
||||
def get_test_empty_captures():
|
||||
return {'id': '806440346Y391300T',
|
||||
'intent': 'CAPTURE',
|
||||
'status': 'COMPLETED',
|
||||
'purchase_units': [{'reference_id': 'default',
|
||||
'amount': {'currency_code': 'EUR', 'value': '43.59'},
|
||||
'payee': {'email_address': 'dummy-facilitator@dummy.dummy',
|
||||
'merchant_id': 'G6R2B9YXADKWW'},
|
||||
'description': 'Order JWJGC for PayPal v2',
|
||||
'custom_id': 'Order PAYPALV2-JWJGC',
|
||||
'soft_descriptor': 'MARTINFACIL',
|
||||
'payments': {'captures': []}
|
||||
}],
|
||||
'payer': {'name': {'given_name': 'test', 'surname': 'buyer'},
|
||||
'email_address': 'dummy@dummy.dummy',
|
||||
'payer_id': 'Q739JNKWH67HE',
|
||||
'address': {'country_code': 'DE'}},
|
||||
'create_time': '2022-04-28T11:59:59Z',
|
||||
'update_time': '2022-04-28T12:00:22Z',
|
||||
'links': [{'href': 'https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T',
|
||||
'rel': 'self',
|
||||
'method': 'GET'}]}
|
||||
|
||||
|
||||
class Object():
|
||||
pass
|
||||
|
||||
@@ -456,6 +480,100 @@ def test_webhook_all_good(env, client, monkeypatch):
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_empty_captures(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
with scopes_disabled():
|
||||
p = order.payments.first()
|
||||
p.state = OrderPayment.PAYMENT_STATE_PENDING
|
||||
p.save()
|
||||
order.status = Order.STATUS_PENDING
|
||||
order.save()
|
||||
|
||||
pp_order = Result(get_test_empty_captures())
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
||||
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
reference="806440346Y391300T")
|
||||
|
||||
client.post('/_paypal/webhook/', json.dumps(
|
||||
{
|
||||
"id": "WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"create_time": "2022-04-28T12:00:37.077Z",
|
||||
"resource_type": "checkout-order",
|
||||
"event_type": "CHECKOUT.ORDER.COMPLETED",
|
||||
"summary": "Checkout Order Completed",
|
||||
"resource": {
|
||||
"update_time": "2022-04-28T12:00:22Z",
|
||||
"create_time": "2022-04-28T11:59:59Z",
|
||||
"purchase_units": [
|
||||
{
|
||||
"reference_id": "default",
|
||||
"amount": {
|
||||
"currency_code": "EUR",
|
||||
"value": "43.59"
|
||||
},
|
||||
"payee": {
|
||||
"email_address": "dummy-facilitator@dummy.dummy",
|
||||
"merchant_id": "G6R2B9YXADKWW"
|
||||
},
|
||||
"description": "Order JWJGC for PayPal v2",
|
||||
"custom_id": "Order PAYPALV2-JWJGC",
|
||||
"soft_descriptor": "MARTINFACIL",
|
||||
"payments": {
|
||||
"captures": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/806440346Y391300T",
|
||||
"rel": "self",
|
||||
"method": "GET"
|
||||
}
|
||||
],
|
||||
"id": "806440346Y391300T",
|
||||
"intent": "CAPTURE",
|
||||
"payer": {
|
||||
"name": {
|
||||
"given_name": "test",
|
||||
"surname": "buyer"
|
||||
},
|
||||
"email_address": "dummy@dummy.dummy",
|
||||
"payer_id": "Q739JNKWH67HE",
|
||||
"address": {
|
||||
"country_code": "DE"
|
||||
}
|
||||
},
|
||||
"status": "COMPLETED"
|
||||
},
|
||||
"status": "SUCCESS",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P",
|
||||
"rel": "self",
|
||||
"method": "GET",
|
||||
"encType": "application/json"
|
||||
},
|
||||
{
|
||||
"href": "https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4T867178D0574904F-7TT11736YU643990P/resend",
|
||||
"rel": "resend",
|
||||
"method": "POST",
|
||||
"encType": "application/json"
|
||||
}
|
||||
],
|
||||
"event_version": "1.0",
|
||||
"resource_version": "2.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
order = env[1]
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
order = env[1]
|
||||
|
||||
@@ -31,6 +31,9 @@ export default defineConfig({
|
||||
// Allow serving source files from sibling plugin directories
|
||||
allow: ['src', ...pluginDirs],
|
||||
},
|
||||
cors: {
|
||||
origin: /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\]|[^:]+\.pretix\.(dev|work))(?::\d+)?$/
|
||||
},
|
||||
},
|
||||
build: {
|
||||
manifest: true,
|
||||
|
||||
Reference in New Issue
Block a user