diff --git a/stripe/_stripe_client.py b/stripe/_stripe_client.py index ff54652f3..bca088b4b 100644 --- a/stripe/_stripe_client.py +++ b/stripe/_stripe_client.py @@ -256,6 +256,10 @@ def construct_event( requestor=self._requestor, api_mode="V1", ) + if event.object == "v2.core.event": # type: ignore + raise ValueError( + "You passed an event notification to StripeClient.construct_event, which expects a webhook payload. Use StripeClient.parse_event_notification instead." + ) return event diff --git a/stripe/_webhook.py b/stripe/_webhook.py index 61686386e..160b88a99 100644 --- a/stripe/_webhook.py +++ b/stripe/_webhook.py @@ -33,6 +33,10 @@ def construct_event( api_mode="V1", ) + if event.object == "v2.core.event": # type: ignore + raise ValueError( + "You passed an event notification to Webhook.construct_event, which expects a webhook payload. Use StripeClient.parse_event_notification instead." + ) return event diff --git a/stripe/v2/core/_event.py b/stripe/v2/core/_event.py index a10e218e9..0e0c6c30e 100644 --- a/stripe/v2/core/_event.py +++ b/stripe/v2/core/_event.py @@ -169,10 +169,14 @@ def __init__( def from_json(payload: str, client: "StripeClient") -> "EventNotification": """ Helper for constructing an Event Notification. Doesn't perform signature validation, so you - should use StripeClient.parseEventNotification() instead for initial handling. + should use StripeClient.parse_event_notification() instead for initial handling. This is useful in unit tests and working with EventNotifications that you've already validated the authenticity of. """ parsed_body = json.loads(payload) + if parsed_body.get("object") == "event": + raise ValueError( + "You passed a webhook payload to StripeClient.parse_event_notification, which expects an event notification. Use StripeClient.construct_event instead." + ) # circular import busting from stripe.events._event_classes import ( diff --git a/tests/test_v2_event.py b/tests/test_v2_event.py index 290efb3b4..55ee13ee2 100644 --- a/tests/test_v2_event.py +++ b/tests/test_v2_event.py @@ -141,6 +141,20 @@ def test_parses_unknown_event_notif(self, parse_event_notif: EventParser): assert type(event) is UnknownEventNotification assert event.related_object + def test_raise_on_v1_payload(self, parse_event_notif: EventParser): + v1_payload = json.dumps( + { + "id": "evt_test_webhook", + "object": "event", + "data": { + "object": {"id": "rdr_123", "object": "terminal.reader"} + }, + } + ) + with pytest.raises(ValueError) as e: + parse_event_notif(v1_payload) + assert "StripeClient.construct_event" in str(e.value) + def test_validates_signature( self, stripe_client: StripeClient, v2_payload_no_data ): diff --git a/tests/test_webhook.py b/tests/test_webhook.py index cd648faa2..5d3856e9b 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -12,6 +12,14 @@ "data": { "object": { "id": "rdr_123", "object": "terminal.reader" } } }""" +DUMMY_V2_WEBHOOK_PAYLOAD = """{ + "id": "evt_234", + "object": "v2.core.event", + "type": "v1.billing.meter.error_report_triggered", + "livemode": true, + "created": "2022-02-15T00:27:45.330Z" +}""" + DUMMY_WEBHOOK_SECRET = "whsec_test_secret" @@ -69,6 +77,14 @@ def test_construct_event_from_bytes(self): ) assert isinstance(event, stripe.Event) + def test_raise_on_v2_payload(self): + header = generate_header(payload=DUMMY_V2_WEBHOOK_PAYLOAD) + with pytest.raises(ValueError) as e: + stripe.Webhook.construct_event( + DUMMY_V2_WEBHOOK_PAYLOAD, header, DUMMY_WEBHOOK_SECRET + ) + assert "StripeClient.parse_event_notification" in str(e.value) + class TestWebhookSignature(object): def test_raise_on_malformed_header(self): @@ -172,6 +188,14 @@ def test_construct_event_from_bytes(self, stripe_mock_stripe_client): ) assert isinstance(event, stripe.Event) + def test_raise_on_v2_payload(self, stripe_mock_stripe_client): + header = generate_header(payload=DUMMY_V2_WEBHOOK_PAYLOAD) + with pytest.raises(ValueError) as e: + stripe_mock_stripe_client.construct_event( + DUMMY_V2_WEBHOOK_PAYLOAD, header, DUMMY_WEBHOOK_SECRET + ) + assert "parse_event_notification" in str(e.value) + def test_construct_event_inherits_requestor(self, http_client_mock): http_client_mock.stub_request("delete", "/v1/terminal/readers/rdr_123")