From ffc560f5ec137eb3baa4f4e674dbb383b4d3daab Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 16 Dec 2024 18:47:38 +0000 Subject: [PATCH] fix: Subscription discount now supports null starts_at --- CHANGELOG.md | 6 +++ paddle_billing/Client.py | 2 +- .../Subscriptions/SubscriptionDiscount.py | 6 +-- .../Subscriptions/SubscriptionDiscount.py | 6 +-- setup.py | 2 +- .../_fixtures/response/list_default.json | 12 ++++- .../Subscriptions/test_SubscriptionsClient.py | 21 +++++++++ .../Notifications/test_NotificationEvent.py | 44 +++++++++++++++++++ .../entity/subscription.created.json | 6 ++- .../entity/subscription.paused.json | 6 ++- 10 files changed, 99 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a354f37..2de33344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-python-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools. +## 1.2.2 - 2024-12-17 + +### Fixed + +- Subscription discount now supports null `starts_at` + ## 1.2.1 - 2024-12-04 ### Fixed diff --git a/paddle_billing/Client.py b/paddle_billing/Client.py index b073d9cd..78e3febb 100644 --- a/paddle_billing/Client.py +++ b/paddle_billing/Client.py @@ -204,7 +204,7 @@ def build_request_session(self) -> Session: "Authorization": f"Bearer {self.__api_key}", "Content-Type": "application/json", "Paddle-Version": str(self.use_api_version), - "User-Agent": "PaddleSDK/python 1.2.1", + "User-Agent": "PaddleSDK/python 1.2.2", } ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionDiscount.py b/paddle_billing/Entities/Subscriptions/SubscriptionDiscount.py index 78389c39..3575abf9 100644 --- a/paddle_billing/Entities/Subscriptions/SubscriptionDiscount.py +++ b/paddle_billing/Entities/Subscriptions/SubscriptionDiscount.py @@ -6,13 +6,13 @@ @dataclass class SubscriptionDiscount: id: str - starts_at: datetime - ends_at: datetime + starts_at: datetime | None + ends_at: datetime | None @staticmethod def from_dict(data: dict) -> SubscriptionDiscount: return SubscriptionDiscount( id=data["id"], - starts_at=datetime.fromisoformat(data["starts_at"]), + starts_at=datetime.fromisoformat(data["starts_at"]) if data.get("starts_at") else None, ends_at=datetime.fromisoformat(data["ends_at"]) if data.get("ends_at") else None, ) diff --git a/paddle_billing/Notifications/Entities/Subscriptions/SubscriptionDiscount.py b/paddle_billing/Notifications/Entities/Subscriptions/SubscriptionDiscount.py index 78389c39..3575abf9 100644 --- a/paddle_billing/Notifications/Entities/Subscriptions/SubscriptionDiscount.py +++ b/paddle_billing/Notifications/Entities/Subscriptions/SubscriptionDiscount.py @@ -6,13 +6,13 @@ @dataclass class SubscriptionDiscount: id: str - starts_at: datetime - ends_at: datetime + starts_at: datetime | None + ends_at: datetime | None @staticmethod def from_dict(data: dict) -> SubscriptionDiscount: return SubscriptionDiscount( id=data["id"], - starts_at=datetime.fromisoformat(data["starts_at"]), + starts_at=datetime.fromisoformat(data["starts_at"]) if data.get("starts_at") else None, ends_at=datetime.fromisoformat(data["ends_at"]) if data.get("ends_at") else None, ) diff --git a/setup.py b/setup.py index 5a88d8c4..53250ddd 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( - version="1.2.1", + version="1.2.2", author="Paddle and contributors", author_email="team-dx@paddle.com", description="Paddle's Python SDK for Paddle Billing", diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json index 43ed30e6..678e5315 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/list_default.json @@ -323,7 +323,11 @@ "update_payment_method": null, "cancel": "https://sandbox-buyer-portal.paddle.com/subscriptions/sub_01hp463gxfvndqjjyqn2n7tkth/cancel" }, - "discount": null, + "discount": { + "id": "dsc_01hv6scyf7qdnzcdq01t2y8dx4", + "starts_at": null, + "ends_at": null + }, "import_meta": null }, { @@ -467,7 +471,11 @@ "update_payment_method": "https://buyer-portal.paddle.com/subscriptions/sub_01hn0epy6nc46wt9hw92pp2kmt/update-payment-method", "cancel": "https://buyer-portal.paddle.com/subscriptions/sub_01hn0epy6nc46wt9hw92pp2kmt/cancel" }, - "discount": null, + "discount": { + "id": "dsc_01hv6scyf7qdnzcdq01t2y8dx4", + "starts_at": "2024-04-12T10:18:47.635628Z", + "ends_at": "2024-05-12T10:18:47.635628Z" + }, "import_meta": null } ], diff --git a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py index f248deb4..9c056c74 100644 --- a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py +++ b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py @@ -371,6 +371,27 @@ def test_list_subscriptions_returns_expected_response( str(expected_response_body) ), "The response JSON generated by ResponseParser() doesn't match the expected fixture JSON" + def test_list_subscriptions_supports_null_discount_starts_at( + self, + test_client, + mock_requests, + ): + mock_requests.get( + f"{test_client.base_url}/subscriptions", + status_code=200, + text=ReadsFixtures.read_raw_json_fixture("response/list_default"), + ) + + response = test_client.client.subscriptions.list(ListSubscriptions()) + + assert isinstance(response, SubscriptionCollection) + assert response.items[0].discount is None + assert response.items[1].discount is None + assert response.items[2].discount.starts_at is None + assert response.items[2].discount.ends_at is None + assert response.items[3].discount.starts_at.isoformat() == "2024-04-12T10:18:47.635628+00:00" + assert response.items[3].discount.ends_at.isoformat() == "2024-05-12T10:18:47.635628+00:00" + @mark.parametrize( "subscription_id, includes, expected_response_status, expected_response_body, expected_url", [ diff --git a/tests/Unit/Entities/Notifications/test_NotificationEvent.py b/tests/Unit/Entities/Notifications/test_NotificationEvent.py index c3c1d9ce..13c99c51 100644 --- a/tests/Unit/Entities/Notifications/test_NotificationEvent.py +++ b/tests/Unit/Entities/Notifications/test_NotificationEvent.py @@ -209,3 +209,47 @@ def test_unknown_event_type_is_handled(self): assert notification_event.occurred_at.isoformat() == "2023-08-21T11:57:47.390028+00:00" assert isinstance(notification_event.data, UndefinedEntity) assert notification_event.data.to_dict()["id"] == "add_01hv8gq3318ktkfengj2r75gfx" + + def test_subscription_notification_event_with_null_discount(self): + notification_event = NotificationEvent.from_dict( + { + "data": loads(ReadsFixtures.read_raw_json_fixture("notification/entity/subscription.trialing")), + "notification_id": "ntf_01h8bkrfe7w1vwf8xmytwn51e7", + "event_type": "subscription.trialing", + "event_id": "evt_01h8bzakzx3hm2fmen703n5q45", + "occurred_at": "2023-08-21T11:57:47.390028Z", + } + ) + + assert isinstance(notification_event.data, Subscription) + assert notification_event.data.discount is None + + def test_subscription_notification_event_discount_without_starts_and_ends_at(self): + notification_event = NotificationEvent.from_dict( + { + "data": loads(ReadsFixtures.read_raw_json_fixture("notification/entity/subscription.paused")), + "notification_id": "ntf_01h8bkrfe7w1vwf8xmytwn51e7", + "event_type": "subscription.paused", + "event_id": "evt_01h8bzakzx3hm2fmen703n5q45", + "occurred_at": "2023-08-21T11:57:47.390028Z", + } + ) + + assert isinstance(notification_event.data, Subscription) + assert notification_event.data.discount.starts_at is None + assert notification_event.data.discount.ends_at is None + + def test_subscription_notification_event_with_discount(self): + notification_event = NotificationEvent.from_dict( + { + "data": loads(ReadsFixtures.read_raw_json_fixture("notification/entity/subscription.created")), + "notification_id": "ntf_01h8bkrfe7w1vwf8xmytwn51e7", + "event_type": "subscription.created", + "event_id": "evt_01h8bzakzx3hm2fmen703n5q45", + "occurred_at": "2023-08-21T11:57:47.390028Z", + } + ) + + assert isinstance(notification_event.data, SubscriptionCreated) + assert notification_event.data.discount.starts_at.isoformat() == "2024-04-12T10:18:47.635628+00:00" + assert notification_event.data.discount.ends_at.isoformat() == "2024-05-12T10:18:47.635628+00:00" diff --git a/tests/Unit/Entities/_fixtures/notification/entity/subscription.created.json b/tests/Unit/Entities/_fixtures/notification/entity/subscription.created.json index 1d8d99b8..3cc4ba14 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/subscription.created.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/subscription.created.json @@ -117,7 +117,11 @@ } ], "status": "active", - "discount": null, + "discount": { + "id": "dsc_01hv6scyf7qdnzcdq01t2y8dx4", + "starts_at": "2024-04-12T10:18:47.635628Z", + "ends_at": "2024-05-12T10:18:47.635628Z" + }, "paused_at": null, "address_id": "add_01hv8gq3318ktkfengj2r75gfx", "created_at": "2024-04-12T10:18:48.831Z", diff --git a/tests/Unit/Entities/_fixtures/notification/entity/subscription.paused.json b/tests/Unit/Entities/_fixtures/notification/entity/subscription.paused.json index d4969c8a..01d78394 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/subscription.paused.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/subscription.paused.json @@ -117,7 +117,11 @@ } ], "status": "paused", - "discount": null, + "discount": { + "id": "dsc_01hv6scyf7qdnzcdq01t2y8dx4", + "starts_at": null, + "ends_at": null + }, "paused_at": "2024-04-12T12:43:43.214Z", "address_id": "add_01hv8gq3318ktkfengj2r75gfx", "created_at": "2024-04-12T12:42:27.89Z",