From e466f673e8a9310332a868b7745f453a21ac49b6 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 10 Nov 2025 17:05:37 +0000 Subject: [PATCH 1/3] feat: Add support for payout reconciliation reports and remittance_reference --- CHANGELOG.md | 6 ++++ .../Reports/PayoutReconciliationReportType.py | 7 ++++ .../Entities/Reports/ReportFilterName.py | 2 ++ paddle_billing/Entities/Reports/ReportType.py | 1 + paddle_billing/Entities/Reports/__init__.py | 1 + .../Notifications/Entities/Payout.py | 2 ++ .../Entities/Reports/ReportFilterName.py | 2 ++ .../Entities/Reports/ReportType.py | 1 + .../Entities/Simulations/Payout.py | 2 ++ .../CreatePayoutReconciliationReport.py | 25 ++++++++++++++ .../Filters/RemittanceReferenceFilter.py | 18 ++++++++++ .../Filters/TransactionUpdatedAtFilter.py | 34 +++++++++++++++++++ .../Reports/Operations/Filters/__init__.py | 2 ++ .../Resources/Reports/Operations/__init__.py | 3 ++ .../create_payout_reconciliation_basic.json | 3 ++ .../create_payout_reconciliation_full.json | 22 ++++++++++++ .../Resources/Reports/test_ReportsClient.py | 23 +++++++++++++ .../notification/entity/payout.created.json | 3 +- .../notification/entity/payout.paid.json | 3 +- 19 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 paddle_billing/Entities/Reports/PayoutReconciliationReportType.py create mode 100644 paddle_billing/Resources/Reports/Operations/CreatePayoutReconciliationReport.py create mode 100644 paddle_billing/Resources/Reports/Operations/Filters/RemittanceReferenceFilter.py create mode 100644 paddle_billing/Resources/Reports/Operations/Filters/TransactionUpdatedAtFilter.py create mode 100644 tests/Functional/Resources/Reports/_fixtures/request/create_payout_reconciliation_basic.json create mode 100644 tests/Functional/Resources/Reports/_fixtures/request/create_payout_reconciliation_full.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f523f72..d768ee13 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 + +- Support for payout reconciliation reports and `remittance_reference`, see [changelog](https://developer.paddle.com/changelog/2025/payout-reconciliation-report) + ## 1.11.0 - 2025-10-07 ### Added diff --git a/paddle_billing/Entities/Reports/PayoutReconciliationReportType.py b/paddle_billing/Entities/Reports/PayoutReconciliationReportType.py new file mode 100644 index 00000000..79c38569 --- /dev/null +++ b/paddle_billing/Entities/Reports/PayoutReconciliationReportType.py @@ -0,0 +1,7 @@ +from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta + +from paddle_billing.Entities.Reports.ReportType import ReportType + + +class PayoutReconciliationReportType(PaddleStrEnum, metaclass=PaddleStrEnumMeta): + PayoutReconciliation: "PayoutReconciliationReportType" = ReportType.PayoutReconciliation.value diff --git a/paddle_billing/Entities/Reports/ReportFilterName.py b/paddle_billing/Entities/Reports/ReportFilterName.py index 855131fe..4d88d3a5 100644 --- a/paddle_billing/Entities/Reports/ReportFilterName.py +++ b/paddle_billing/Entities/Reports/ReportFilterName.py @@ -12,6 +12,8 @@ class ReportFilterName(PaddleStrEnum, metaclass=PaddleStrEnumMeta): ProductStatus: "ReportFilterName" = "product_status" ProductType: "ReportFilterName" = "product_type" ProductUpdatedAt: "ReportFilterName" = "product_updated_at" + RemittanceReference: "ReportFilterName" = "remittance_reference" Status: "ReportFilterName" = "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 5e210b62..f2d1c504 100644 --- a/paddle_billing/Entities/Reports/ReportType.py +++ b/paddle_billing/Entities/Reports/ReportType.py @@ -9,3 +9,4 @@ class ReportType(PaddleStrEnum, metaclass=PaddleStrEnumMeta): Transactions: "ReportType" = "transactions" TransactionLineItems: "ReportType" = "transaction_line_items" Balance: "ReportType" = "balance" + PayoutReconciliation: "ReportType" = "payout_reconciliation" diff --git a/paddle_billing/Entities/Reports/__init__.py b/paddle_billing/Entities/Reports/__init__.py index 60271882..5f0b0286 100644 --- a/paddle_billing/Entities/Reports/__init__.py +++ b/paddle_billing/Entities/Reports/__init__.py @@ -1,6 +1,7 @@ from paddle_billing.Entities.Reports.AdjustmentsReportType import AdjustmentsReportType from paddle_billing.Entities.Reports.BalanceReportType import BalanceReportType from paddle_billing.Entities.Reports.DiscountsReportType import DiscountsReportType +from paddle_billing.Entities.Reports.PayoutReconciliationReportType import PayoutReconciliationReportType from paddle_billing.Entities.Reports.ProductsPricesReportType import ProductsPricesReportType from paddle_billing.Entities.Reports.ReportFilter import ReportFilter from paddle_billing.Entities.Reports.ReportFilterName import ReportFilterName diff --git a/paddle_billing/Notifications/Entities/Payout.py b/paddle_billing/Notifications/Entities/Payout.py index 7b91457b..13ca2797 100644 --- a/paddle_billing/Notifications/Entities/Payout.py +++ b/paddle_billing/Notifications/Entities/Payout.py @@ -13,6 +13,7 @@ class Payout(Entity): currency_code: CurrencyCodePayouts id: str status: PayoutStatus + remittance_reference: str | None @staticmethod def from_dict(data: dict[str, Any]) -> Payout: @@ -21,4 +22,5 @@ def from_dict(data: dict[str, Any]) -> Payout: id=data["id"], status=PayoutStatus(data["status"]), currency_code=CurrencyCodePayouts(data["currency_code"]), + remittance_reference=data.get("remittance_reference"), ) diff --git a/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py b/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py index 855131fe..4d88d3a5 100644 --- a/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py +++ b/paddle_billing/Notifications/Entities/Reports/ReportFilterName.py @@ -12,6 +12,8 @@ class ReportFilterName(PaddleStrEnum, metaclass=PaddleStrEnumMeta): ProductStatus: "ReportFilterName" = "product_status" ProductType: "ReportFilterName" = "product_type" ProductUpdatedAt: "ReportFilterName" = "product_updated_at" + RemittanceReference: "ReportFilterName" = "remittance_reference" Status: "ReportFilterName" = "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 5e210b62..f2d1c504 100644 --- a/paddle_billing/Notifications/Entities/Reports/ReportType.py +++ b/paddle_billing/Notifications/Entities/Reports/ReportType.py @@ -9,3 +9,4 @@ class ReportType(PaddleStrEnum, metaclass=PaddleStrEnumMeta): Transactions: "ReportType" = "transactions" TransactionLineItems: "ReportType" = "transaction_line_items" Balance: "ReportType" = "balance" + PayoutReconciliation: "ReportType" = "payout_reconciliation" diff --git a/paddle_billing/Notifications/Entities/Simulations/Payout.py b/paddle_billing/Notifications/Entities/Simulations/Payout.py index 28d5970b..54c67fc5 100644 --- a/paddle_billing/Notifications/Entities/Simulations/Payout.py +++ b/paddle_billing/Notifications/Entities/Simulations/Payout.py @@ -14,6 +14,7 @@ class Payout(SimulationEntity): currency_code: CurrencyCodePayouts | Undefined = Undefined() id: str | Undefined = Undefined() status: PayoutStatus | Undefined = Undefined() + remittance_reference: str | Undefined = Undefined() @staticmethod def from_dict(data: dict[str, Any]) -> Payout: @@ -22,4 +23,5 @@ def from_dict(data: dict[str, Any]) -> Payout: id=data.get("id", Undefined()), status=PayoutStatus(data["status"]) if data.get("status") else Undefined(), currency_code=CurrencyCodePayouts(data["currency_code"]) if data.get("currency_code") else Undefined(), + remittance_reference=data.get("remittance_reference", Undefined()), ) diff --git a/paddle_billing/Resources/Reports/Operations/CreatePayoutReconciliationReport.py b/paddle_billing/Resources/Reports/Operations/CreatePayoutReconciliationReport.py new file mode 100644 index 00000000..07d8e8ee --- /dev/null +++ b/paddle_billing/Resources/Reports/Operations/CreatePayoutReconciliationReport.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass, field + +from paddle_billing.Entities.Reports import PayoutReconciliationReportType + +from paddle_billing.Resources.Reports.Operations.Filters import ( + RemittanceReferenceFilter, + TransactionUpdatedAtFilter, +) + +from paddle_billing.Resources.Reports.Operations.CreateReport import ( + CreateReport, +) + + +@dataclass +class CreatePayoutReconciliationReport(CreateReport): + type: PayoutReconciliationReportType + filters: list[RemittanceReferenceFilter | TransactionUpdatedAtFilter] = field(default_factory=list) + + @staticmethod + def get_allowed_filters() -> tuple[RemittanceReferenceFilter | TransactionUpdatedAtFilter]: + return ( + RemittanceReferenceFilter, + TransactionUpdatedAtFilter, + ) diff --git a/paddle_billing/Resources/Reports/Operations/Filters/RemittanceReferenceFilter.py b/paddle_billing/Resources/Reports/Operations/Filters/RemittanceReferenceFilter.py new file mode 100644 index 00000000..e92618c4 --- /dev/null +++ b/paddle_billing/Resources/Reports/Operations/Filters/RemittanceReferenceFilter.py @@ -0,0 +1,18 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Resources.Reports.Operations.Filters.Filter import Filter + +from paddle_billing.Entities.Reports import ReportFilterName + + +@dataclass +class RemittanceReferenceFilter(Filter): + remittance_references: list[str] + + @staticmethod + def get_name() -> ReportFilterName: + return ReportFilterName.RemittanceReference + + def get_value(self) -> list[str]: + return self.remittance_references diff --git a/paddle_billing/Resources/Reports/Operations/Filters/TransactionUpdatedAtFilter.py b/paddle_billing/Resources/Reports/Operations/Filters/TransactionUpdatedAtFilter.py new file mode 100644 index 00000000..fa38e9c1 --- /dev/null +++ b/paddle_billing/Resources/Reports/Operations/Filters/TransactionUpdatedAtFilter.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 TransactionUpdatedAtFilter(Filter): + operator: ReportFilterOperator + value: DateTime + + @staticmethod + def get_name() -> ReportFilterName: + return ReportFilterName.TransactionUpdatedAt + + def get_operator(self) -> ReportFilterOperator | None: + return self.operator + + def get_value(self) -> str: + return self.value.format() + + @staticmethod + def gte(value: DateTime) -> TransactionUpdatedAtFilter: + return TransactionUpdatedAtFilter(value=value, operator=ReportFilterOperator.Gte) + + @staticmethod + def lt(value: DateTime) -> TransactionUpdatedAtFilter: + return TransactionUpdatedAtFilter(value=value, operator=ReportFilterOperator.Lt) diff --git a/paddle_billing/Resources/Reports/Operations/Filters/__init__.py b/paddle_billing/Resources/Reports/Operations/Filters/__init__.py index 4dede175..cca810dc 100644 --- a/paddle_billing/Resources/Reports/Operations/Filters/__init__.py +++ b/paddle_billing/Resources/Reports/Operations/Filters/__init__.py @@ -10,6 +10,8 @@ from paddle_billing.Resources.Reports.Operations.Filters.ProductStatusFilter import ProductStatusFilter 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.TransactionOriginFilter import TransactionOriginFilter from paddle_billing.Resources.Reports.Operations.Filters.TransactionStatusFilter import TransactionStatusFilter +from paddle_billing.Resources.Reports.Operations.Filters.TransactionUpdatedAtFilter import TransactionUpdatedAtFilter from paddle_billing.Resources.Reports.Operations.Filters.UpdatedAtFilter import UpdatedAtFilter diff --git a/paddle_billing/Resources/Reports/Operations/__init__.py b/paddle_billing/Resources/Reports/Operations/__init__.py index 21b86bf0..be380000 100644 --- a/paddle_billing/Resources/Reports/Operations/__init__.py +++ b/paddle_billing/Resources/Reports/Operations/__init__.py @@ -1,6 +1,9 @@ from paddle_billing.Resources.Reports.Operations.CreateAdjustmentsReport import CreateAdjustmentsReport from paddle_billing.Resources.Reports.Operations.CreateBalanceReport import CreateBalanceReport from paddle_billing.Resources.Reports.Operations.CreateDiscountsReport import CreateDiscountsReport +from paddle_billing.Resources.Reports.Operations.CreatePayoutReconciliationReport import ( + CreatePayoutReconciliationReport, +) from paddle_billing.Resources.Reports.Operations.CreateProductsAndPricesReport import CreateProductsAndPricesReport 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_payout_reconciliation_basic.json b/tests/Functional/Resources/Reports/_fixtures/request/create_payout_reconciliation_basic.json new file mode 100644 index 00000000..9219fa27 --- /dev/null +++ b/tests/Functional/Resources/Reports/_fixtures/request/create_payout_reconciliation_basic.json @@ -0,0 +1,3 @@ +{ + "type": "payout_reconciliation" +} diff --git a/tests/Functional/Resources/Reports/_fixtures/request/create_payout_reconciliation_full.json b/tests/Functional/Resources/Reports/_fixtures/request/create_payout_reconciliation_full.json new file mode 100644 index 00000000..89aa3773 --- /dev/null +++ b/tests/Functional/Resources/Reports/_fixtures/request/create_payout_reconciliation_full.json @@ -0,0 +1,22 @@ +{ + "type": "payout_reconciliation", + "filters": [ + { + "name": "remittance_reference", + "operator": null, + "value": [ + "some-reference" + ] + }, + { + "name": "transaction_updated_at", + "operator": "gte", + "value": "2023-12-30T00:00:00.000000Z" + }, + { + "name": "transaction_updated_at", + "operator": "lt", + "value": "2024-12-30T12:30:01.123456Z" + } + ] +} diff --git a/tests/Functional/Resources/Reports/test_ReportsClient.py b/tests/Functional/Resources/Reports/test_ReportsClient.py index c9283a12..79cf2c98 100644 --- a/tests/Functional/Resources/Reports/test_ReportsClient.py +++ b/tests/Functional/Resources/Reports/test_ReportsClient.py @@ -9,6 +9,7 @@ AdjustmentsReportType, BalanceReportType, DiscountsReportType, + PayoutReconciliationReportType, ProductsPricesReportType, ReportFilter, ReportFilterName, @@ -32,8 +33,10 @@ ProductStatusFilter, ProductTypeFilter, ProductUpdatedAtFilter, + RemittanceReferenceFilter, TransactionOriginFilter, TransactionStatusFilter, + TransactionUpdatedAtFilter, UpdatedAtFilter, ) @@ -59,6 +62,7 @@ CreateAdjustmentsReport, CreateBalanceReport, CreateDiscountsReport, + CreatePayoutReconciliationReport, CreateProductsAndPricesReport, CreateTransactionsReport, ListReports, @@ -153,6 +157,23 @@ class TestReportsClient: ReadsFixtures.read_raw_json_fixture("request/create_balance_full"), BalanceReportType, ), + ( + CreatePayoutReconciliationReport(type=PayoutReconciliationReportType.PayoutReconciliation), + ReadsFixtures.read_raw_json_fixture("request/create_payout_reconciliation_basic"), + PayoutReconciliationReportType, + ), + ( + CreatePayoutReconciliationReport( + type=PayoutReconciliationReportType.PayoutReconciliation, + filters=[ + RemittanceReferenceFilter(["some-reference"]), + TransactionUpdatedAtFilter.gte(DateTime("2023-12-30")), + TransactionUpdatedAtFilter.lt(DateTime("2024-12-30T12:30:01.123456Z")), + ], + ), + ReadsFixtures.read_raw_json_fixture("request/create_payout_reconciliation_full"), + PayoutReconciliationReportType, + ), ], ids=[ "Create transaction report with basic data", @@ -162,6 +183,8 @@ class TestReportsClient: "Create discount report with filters", "Create products and prices report with filters", "Create balance report with filters", + "Create payout reconciliation report with basic data", + "Create payout reconciliation report with filters", ], ) def test_create_report_uses_expected_payload( diff --git a/tests/Unit/Entities/_fixtures/notification/entity/payout.created.json b/tests/Unit/Entities/_fixtures/notification/entity/payout.created.json index 072417d8..19b455d0 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/payout.created.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/payout.created.json @@ -2,5 +2,6 @@ "id": "pay_01gsz4vmqbjk3x4vvtafffd540", "status": "unpaid", "amount": "10000", - "currency_code": "USD" + "currency_code": "USD", + "remittance_reference": "some-reference" } \ No newline at end of file diff --git a/tests/Unit/Entities/_fixtures/notification/entity/payout.paid.json b/tests/Unit/Entities/_fixtures/notification/entity/payout.paid.json index 8db93222..d2cda8fd 100644 --- a/tests/Unit/Entities/_fixtures/notification/entity/payout.paid.json +++ b/tests/Unit/Entities/_fixtures/notification/entity/payout.paid.json @@ -2,5 +2,6 @@ "id": "pay_01gsz4vmqbjk3x4vvtafffd540", "status": "paid", "amount": "10000", - "currency_code": "USD" + "currency_code": "USD", + "remittance_reference": "some-reference" } \ No newline at end of file From 9bbb2f343233a53163c2218dd740e731f6658e1a Mon Sep 17 00:00:00 2001 From: David Grayston Date: Mon, 10 Nov 2025 17:34:37 +0000 Subject: [PATCH 2/3] fix: Fixed ReportsClient.create operation type --- CHANGELOG.md | 3 +++ paddle_billing/Resources/Reports/Operations/__init__.py | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d768ee13..8f1c28c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx - Support for payout reconciliation reports and `remittance_reference`, see [changelog](https://developer.paddle.com/changelog/2025/payout-reconciliation-report) +### Fixed +- Fixed `ReportsClient.create()` operation type + ## 1.11.0 - 2025-10-07 ### Added diff --git a/paddle_billing/Resources/Reports/Operations/__init__.py b/paddle_billing/Resources/Reports/Operations/__init__.py index be380000..5a3d9d04 100644 --- a/paddle_billing/Resources/Reports/Operations/__init__.py +++ b/paddle_billing/Resources/Reports/Operations/__init__.py @@ -5,5 +5,6 @@ CreatePayoutReconciliationReport, ) from paddle_billing.Resources.Reports.Operations.CreateProductsAndPricesReport import CreateProductsAndPricesReport +from paddle_billing.Resources.Reports.Operations.CreateReport import CreateReport from paddle_billing.Resources.Reports.Operations.CreateTransactionsReport import CreateTransactionsReport from paddle_billing.Resources.Reports.Operations.ListReports import ListReports From d275d5cab369088e60c297a7fb5545749fec78f9 Mon Sep 17 00:00:00 2001 From: David Grayston Date: Tue, 11 Nov 2025 12:14:04 +0000 Subject: [PATCH 3/3] feat: Added location value for price.tax_mode --- CHANGELOG.md | 3 ++- paddle_billing/Entities/Shared/TaxMode.py | 1 + paddle_billing/Notifications/Entities/Shared/TaxMode.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1c28c5..46b76423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx ### Added -- Support for payout reconciliation reports and `remittance_reference`, see [changelog](https://developer.paddle.com/changelog/2025/payout-reconciliation-report) +- Support for payout reconciliation reports and `remittance_reference`, see [changelog](https://developer.paddle.com/changelog/2025/payout-reconciliation-report?utm_source=dx&utm_medium=paddle-python-sdk) +- Added `location` value for `price.tax_mode`, see [changelog](https://developer.paddle.com/changelog/2025/default-automatic-tax-setting?utm_source=dx&utm_medium=paddle-python-sdk) ### Fixed - Fixed `ReportsClient.create()` operation type diff --git a/paddle_billing/Entities/Shared/TaxMode.py b/paddle_billing/Entities/Shared/TaxMode.py index 1de28f46..2f55dc47 100644 --- a/paddle_billing/Entities/Shared/TaxMode.py +++ b/paddle_billing/Entities/Shared/TaxMode.py @@ -5,3 +5,4 @@ class TaxMode(PaddleStrEnum, metaclass=PaddleStrEnumMeta): AccountSetting: "TaxMode" = "account_setting" External: "TaxMode" = "external" Internal: "TaxMode" = "internal" + Location: "TaxMode" = "location" diff --git a/paddle_billing/Notifications/Entities/Shared/TaxMode.py b/paddle_billing/Notifications/Entities/Shared/TaxMode.py index 1de28f46..2f55dc47 100644 --- a/paddle_billing/Notifications/Entities/Shared/TaxMode.py +++ b/paddle_billing/Notifications/Entities/Shared/TaxMode.py @@ -5,3 +5,4 @@ class TaxMode(PaddleStrEnum, metaclass=PaddleStrEnumMeta): AccountSetting: "TaxMode" = "account_setting" External: "TaxMode" = "external" Internal: "TaxMode" = "internal" + Location: "TaxMode" = "location"