diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b0d71ba..0939a793 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. +## Unreleased + +### Added + +- Added support for `subscriptions` report type + ## 1.15.0 - 2026-07-28 ### Added diff --git a/paddle_billing/Entities/Reports/ReportFilterName.py b/paddle_billing/Entities/Reports/ReportFilterName.py index f8383ceb..45ad7cbb 100644 --- a/paddle_billing/Entities/Reports/ReportFilterName.py +++ b/paddle_billing/Entities/Reports/ReportFilterName.py @@ -18,6 +18,9 @@ class ReportFilterName(PaddleStrEnum, metaclass=PaddleStrEnumMeta): ProductUpdatedAt: "ReportFilterName" = "product_updated_at" RemittanceReference: "ReportFilterName" = "remittance_reference" Status: "ReportFilterName" = "status" + SubscriptionCreatedAt: "ReportFilterName" = "subscription_created_at" + SubscriptionScheduledChangeAction: "ReportFilterName" = "subscription_scheduled_change_action" + SubscriptionStatus: "ReportFilterName" = "subscription_status" TransactionUpdatedAt: "ReportFilterName" = "transaction_updated_at" Type: "ReportFilterName" = "type" UpdatedAt: "ReportFilterName" = "updated_at" diff --git a/paddle_billing/Entities/Reports/ReportType.py b/paddle_billing/Entities/Reports/ReportType.py index 403b8fe9..a3e269c1 100644 --- a/paddle_billing/Entities/Reports/ReportType.py +++ b/paddle_billing/Entities/Reports/ReportType.py @@ -11,3 +11,4 @@ class ReportType(PaddleStrEnum, metaclass=PaddleStrEnumMeta): Balance: "ReportType" = "balance" PayoutReconciliation: "ReportType" = "payout_reconciliation" Checkouts: "ReportType" = "checkouts" + Subscriptions: "ReportType" = "subscriptions" diff --git a/paddle_billing/Entities/Reports/SubscriptionScheduledChangeActionFilterValue.py b/paddle_billing/Entities/Reports/SubscriptionScheduledChangeActionFilterValue.py new file mode 100644 index 00000000..89c95ce8 --- /dev/null +++ b/paddle_billing/Entities/Reports/SubscriptionScheduledChangeActionFilterValue.py @@ -0,0 +1,8 @@ +from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta + + +class SubscriptionScheduledChangeActionFilterValue(PaddleStrEnum, metaclass=PaddleStrEnumMeta): + Cancel: "SubscriptionScheduledChangeActionFilterValue" = "cancel" + Pause: "SubscriptionScheduledChangeActionFilterValue" = "pause" + Resume: "SubscriptionScheduledChangeActionFilterValue" = "resume" + NoChange: "SubscriptionScheduledChangeActionFilterValue" = "none" diff --git a/paddle_billing/Entities/Reports/SubscriptionStatusFilterValue.py b/paddle_billing/Entities/Reports/SubscriptionStatusFilterValue.py new file mode 100644 index 00000000..3b897dd6 --- /dev/null +++ b/paddle_billing/Entities/Reports/SubscriptionStatusFilterValue.py @@ -0,0 +1,9 @@ +from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta + + +class SubscriptionStatusFilterValue(PaddleStrEnum, metaclass=PaddleStrEnumMeta): + Active: "SubscriptionStatusFilterValue" = "active" + Canceled: "SubscriptionStatusFilterValue" = "canceled" + PastDue: "SubscriptionStatusFilterValue" = "past_due" + Paused: "SubscriptionStatusFilterValue" = "paused" + Trialing: "SubscriptionStatusFilterValue" = "trialing" diff --git a/paddle_billing/Entities/Reports/SubscriptionsReportType.py b/paddle_billing/Entities/Reports/SubscriptionsReportType.py new file mode 100644 index 00000000..844b6bd8 --- /dev/null +++ b/paddle_billing/Entities/Reports/SubscriptionsReportType.py @@ -0,0 +1,7 @@ +from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta + +from paddle_billing.Entities.Reports.ReportType import ReportType + + +class SubscriptionsReportType(PaddleStrEnum, metaclass=PaddleStrEnumMeta): + Subscriptions: "SubscriptionsReportType" = ReportType.Subscriptions.value diff --git a/paddle_billing/Entities/Reports/__init__.py b/paddle_billing/Entities/Reports/__init__.py index 3f46884c..a45909ce 100644 --- a/paddle_billing/Entities/Reports/__init__.py +++ b/paddle_billing/Entities/Reports/__init__.py @@ -10,4 +10,9 @@ from paddle_billing.Entities.Reports.ReportFilterOperator import ReportFilterOperator from paddle_billing.Entities.Reports.ReportStatus import ReportStatus from paddle_billing.Entities.Reports.ReportType import ReportType +from paddle_billing.Entities.Reports.SubscriptionScheduledChangeActionFilterValue import ( + SubscriptionScheduledChangeActionFilterValue, +) +from paddle_billing.Entities.Reports.SubscriptionStatusFilterValue import SubscriptionStatusFilterValue +from paddle_billing.Entities.Reports.SubscriptionsReportType import SubscriptionsReportType from paddle_billing.Entities.Reports.TransactionsReportType import TransactionsReportType diff --git a/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py b/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py index f8383ceb..45ad7cbb 100644 --- a/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py +++ b/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py @@ -18,6 +18,9 @@ class ReportFilterName(PaddleStrEnum, metaclass=PaddleStrEnumMeta): ProductUpdatedAt: "ReportFilterName" = "product_updated_at" RemittanceReference: "ReportFilterName" = "remittance_reference" Status: "ReportFilterName" = "status" + SubscriptionCreatedAt: "ReportFilterName" = "subscription_created_at" + SubscriptionScheduledChangeAction: "ReportFilterName" = "subscription_scheduled_change_action" + SubscriptionStatus: "ReportFilterName" = "subscription_status" TransactionUpdatedAt: "ReportFilterName" = "transaction_updated_at" Type: "ReportFilterName" = "type" UpdatedAt: "ReportFilterName" = "updated_at" diff --git a/paddle_billing/Notifications/Entities/Reports/ReportType.py b/paddle_billing/Notifications/Entities/Reports/ReportType.py index 403b8fe9..a3e269c1 100644 --- a/paddle_billing/Notifications/Entities/Reports/ReportType.py +++ b/paddle_billing/Notifications/Entities/Reports/ReportType.py @@ -11,3 +11,4 @@ class ReportType(PaddleStrEnum, metaclass=PaddleStrEnumMeta): Balance: "ReportType" = "balance" PayoutReconciliation: "ReportType" = "payout_reconciliation" Checkouts: "ReportType" = "checkouts" + Subscriptions: "ReportType" = "subscriptions" diff --git a/paddle_billing/Resources/Reports/Operations/CreateSubscriptionsReport.py b/paddle_billing/Resources/Reports/Operations/CreateSubscriptionsReport.py new file mode 100644 index 00000000..535a7fe7 --- /dev/null +++ b/paddle_billing/Resources/Reports/Operations/CreateSubscriptionsReport.py @@ -0,0 +1,41 @@ +from dataclasses import dataclass, field + +from paddle_billing.Entities.Reports import SubscriptionsReportType + +from paddle_billing.Resources.Reports.Operations.Filters import ( + CustomerCountryCodeFilter, + SubscriptionCreatedAtFilter, + SubscriptionScheduledChangeActionFilter, + SubscriptionStatusFilter, +) + +from paddle_billing.Resources.Reports.Operations.CreateReport import ( + CreateReport, +) + + +@dataclass +class CreateSubscriptionsReport(CreateReport): + type: SubscriptionsReportType + filters: list[ + CustomerCountryCodeFilter + | SubscriptionCreatedAtFilter + | SubscriptionScheduledChangeActionFilter + | SubscriptionStatusFilter + ] = field(default_factory=list) + + @staticmethod + def get_allowed_filters() -> ( + tuple[ + CustomerCountryCodeFilter + | SubscriptionCreatedAtFilter + | SubscriptionScheduledChangeActionFilter + | SubscriptionStatusFilter + ] + ): + return ( + CustomerCountryCodeFilter, + SubscriptionCreatedAtFilter, + SubscriptionScheduledChangeActionFilter, + SubscriptionStatusFilter, + ) diff --git a/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionCreatedAtFilter.py b/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionCreatedAtFilter.py new file mode 100644 index 00000000..9d0e5f65 --- /dev/null +++ b/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionCreatedAtFilter.py @@ -0,0 +1,34 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Entities.DateTime import DateTime + +from paddle_billing.Entities.Reports import ReportFilterOperator + +from paddle_billing.Resources.Reports.Operations.Filters.Filter import Filter + +from paddle_billing.Entities.Reports import ReportFilterName + + +@dataclass +class SubscriptionCreatedAtFilter(Filter): + operator: ReportFilterOperator + value: DateTime + + @staticmethod + def get_name() -> ReportFilterName: + return ReportFilterName.SubscriptionCreatedAt + + def get_operator(self) -> ReportFilterOperator | None: + return self.operator + + def get_value(self) -> str: + return self.value.format() + + @staticmethod + def gte(value: DateTime) -> SubscriptionCreatedAtFilter: + return SubscriptionCreatedAtFilter(value=value, operator=ReportFilterOperator.Gte) + + @staticmethod + def lt(value: DateTime) -> SubscriptionCreatedAtFilter: + return SubscriptionCreatedAtFilter(value=value, operator=ReportFilterOperator.Lt) diff --git a/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionScheduledChangeActionFilter.py b/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionScheduledChangeActionFilter.py new file mode 100644 index 00000000..ce70b322 --- /dev/null +++ b/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionScheduledChangeActionFilter.py @@ -0,0 +1,22 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.EnumStringify import enum_stringify + +from paddle_billing.Resources.Reports.Operations.Filters.Filter import Filter + +from paddle_billing.Entities.Reports import SubscriptionScheduledChangeActionFilterValue + +from paddle_billing.Entities.Reports import ReportFilterName + + +@dataclass +class SubscriptionScheduledChangeActionFilter(Filter): + actions: list[SubscriptionScheduledChangeActionFilterValue] + + @staticmethod + def get_name() -> ReportFilterName: + return ReportFilterName.SubscriptionScheduledChangeAction + + def get_value(self) -> list[str]: + return list(map(enum_stringify, self.actions)) diff --git a/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionStatusFilter.py b/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionStatusFilter.py new file mode 100644 index 00000000..ecd004ab --- /dev/null +++ b/paddle_billing/Resources/Reports/Operations/Filters/SubscriptionStatusFilter.py @@ -0,0 +1,22 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.EnumStringify import enum_stringify + +from paddle_billing.Resources.Reports.Operations.Filters.Filter import Filter + +from paddle_billing.Entities.Reports import SubscriptionStatusFilterValue + +from paddle_billing.Entities.Reports import ReportFilterName + + +@dataclass +class SubscriptionStatusFilter(Filter): + statuses: list[SubscriptionStatusFilterValue] + + @staticmethod + def get_name() -> ReportFilterName: + return ReportFilterName.SubscriptionStatus + + def get_value(self) -> list[str]: + return list(map(enum_stringify, self.statuses)) diff --git a/paddle_billing/Resources/Reports/Operations/Filters/__init__.py b/paddle_billing/Resources/Reports/Operations/Filters/__init__.py index 534e3324..e688d059 100644 --- a/paddle_billing/Resources/Reports/Operations/Filters/__init__.py +++ b/paddle_billing/Resources/Reports/Operations/Filters/__init__.py @@ -15,6 +15,11 @@ from paddle_billing.Resources.Reports.Operations.Filters.ProductTypeFilter import ProductTypeFilter from paddle_billing.Resources.Reports.Operations.Filters.ProductUpdatedAtFilter import ProductUpdatedAtFilter from paddle_billing.Resources.Reports.Operations.Filters.RemittanceReferenceFilter import RemittanceReferenceFilter +from paddle_billing.Resources.Reports.Operations.Filters.SubscriptionCreatedAtFilter import SubscriptionCreatedAtFilter +from paddle_billing.Resources.Reports.Operations.Filters.SubscriptionScheduledChangeActionFilter import ( + SubscriptionScheduledChangeActionFilter, +) +from paddle_billing.Resources.Reports.Operations.Filters.SubscriptionStatusFilter import SubscriptionStatusFilter from paddle_billing.Resources.Reports.Operations.Filters.TransactionOriginFilter import TransactionOriginFilter from paddle_billing.Resources.Reports.Operations.Filters.TransactionStatusFilter import TransactionStatusFilter from paddle_billing.Resources.Reports.Operations.Filters.TransactionUpdatedAtFilter import TransactionUpdatedAtFilter diff --git a/paddle_billing/Resources/Reports/Operations/__init__.py b/paddle_billing/Resources/Reports/Operations/__init__.py index 36cee0ea..aeb549d7 100644 --- a/paddle_billing/Resources/Reports/Operations/__init__.py +++ b/paddle_billing/Resources/Reports/Operations/__init__.py @@ -7,5 +7,6 @@ ) from paddle_billing.Resources.Reports.Operations.CreateProductsAndPricesReport import CreateProductsAndPricesReport from paddle_billing.Resources.Reports.Operations.CreateReport import CreateReport +from paddle_billing.Resources.Reports.Operations.CreateSubscriptionsReport import CreateSubscriptionsReport from paddle_billing.Resources.Reports.Operations.CreateTransactionsReport import CreateTransactionsReport from paddle_billing.Resources.Reports.Operations.ListReports import ListReports diff --git a/tests/Functional/Resources/Reports/_fixtures/request/create_subscriptions_basic.json b/tests/Functional/Resources/Reports/_fixtures/request/create_subscriptions_basic.json new file mode 100644 index 00000000..498de9e5 --- /dev/null +++ b/tests/Functional/Resources/Reports/_fixtures/request/create_subscriptions_basic.json @@ -0,0 +1,3 @@ +{ + "type": "subscriptions" +} diff --git a/tests/Functional/Resources/Reports/_fixtures/request/create_subscriptions_full.json b/tests/Functional/Resources/Reports/_fixtures/request/create_subscriptions_full.json new file mode 100644 index 00000000..3064f4b9 --- /dev/null +++ b/tests/Functional/Resources/Reports/_fixtures/request/create_subscriptions_full.json @@ -0,0 +1,39 @@ +{ + "type": "subscriptions", + "filters": [ + { + "name": "subscription_status", + "operator": null, + "value": [ + "active", + "past_due" + ] + }, + { + "name": "subscription_created_at", + "operator": "gte", + "value": "2023-12-30T00:00:00.000000Z" + }, + { + "name": "subscription_created_at", + "operator": "lt", + "value": "2024-12-30T12:30:01.123456Z" + }, + { + "name": "customer_country_code", + "operator": null, + "value": [ + "US", + "GB" + ] + }, + { + "name": "subscription_scheduled_change_action", + "operator": null, + "value": [ + "cancel", + "none" + ] + } + ] +} diff --git a/tests/Functional/Resources/Reports/test_ReportsClient.py b/tests/Functional/Resources/Reports/test_ReportsClient.py index 1ff1c787..f2b5a832 100644 --- a/tests/Functional/Resources/Reports/test_ReportsClient.py +++ b/tests/Functional/Resources/Reports/test_ReportsClient.py @@ -13,6 +13,9 @@ DiscountsReportType, PayoutReconciliationReportType, ProductsPricesReportType, + SubscriptionScheduledChangeActionFilterValue, + SubscriptionStatusFilterValue, + SubscriptionsReportType, ReportFilter, ReportFilterName, ReportFilterOperator, @@ -39,6 +42,9 @@ ProductStatusFilter, ProductTypeFilter, ProductUpdatedAtFilter, + SubscriptionCreatedAtFilter, + SubscriptionScheduledChangeActionFilter, + SubscriptionStatusFilter, RemittanceReferenceFilter, TransactionOriginFilter, TransactionStatusFilter, @@ -72,6 +78,7 @@ CreateDiscountsReport, CreatePayoutReconciliationReport, CreateProductsAndPricesReport, + CreateSubscriptionsReport, CreateTransactionsReport, ListReports, ) @@ -199,6 +206,32 @@ class TestReportsClient: ReadsFixtures.read_raw_json_fixture("request/create_checkouts_full"), CheckoutsReportType, ), + ( + CreateSubscriptionsReport(type=SubscriptionsReportType.Subscriptions), + ReadsFixtures.read_raw_json_fixture("request/create_subscriptions_basic"), + SubscriptionsReportType, + ), + ( + CreateSubscriptionsReport( + type=SubscriptionsReportType.Subscriptions, + filters=[ + SubscriptionStatusFilter( + [SubscriptionStatusFilterValue.Active, SubscriptionStatusFilterValue.PastDue] + ), + SubscriptionCreatedAtFilter.gte(DateTime("2023-12-30")), + SubscriptionCreatedAtFilter.lt(DateTime("2024-12-30T12:30:01.123456Z")), + CustomerCountryCodeFilter([CountryCode.US, CountryCode.GB]), + SubscriptionScheduledChangeActionFilter( + [ + SubscriptionScheduledChangeActionFilterValue.Cancel, + SubscriptionScheduledChangeActionFilterValue.NoChange, + ] + ), + ], + ), + ReadsFixtures.read_raw_json_fixture("request/create_subscriptions_full"), + SubscriptionsReportType, + ), ( CreatePayoutReconciliationReport( type=PayoutReconciliationReportType.PayoutReconciliation, @@ -230,6 +263,8 @@ class TestReportsClient: "Create payout reconciliation report with filters", "Create checkouts report with basic data", "Create checkouts report with filters", + "Create subscriptions report with basic data", + "Create subscriptions report with filters", "Create payout reconciliation report with balance movement filters", ], )