diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3b5a03..dc67fcc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,20 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx - `paddle_billing.Resources.Discounts.Operations.CreateDiscount` `expires_at` is now `paddle_billing.Entities.DateTime` - `paddle_billing.Resources.Discounts.Operations.UpdateDiscount` `expires_at` is now `paddle_billing.Entities.DateTime` +- Transaction and Subscription operation items now allow optional properties to be omitted. + - The following property types have changed (See UPGRADING.md for further details) + - `paddle_billing.Resources.Subscriptions.Operations`: + - `UpdateSubscription.items` + - `PreviewUpdateSubscription.items` + - `CreateOneTimeCharge.items` + - `PreviewOneTimeCharge.items` + - `paddle_billing.Resources.Transactions.Operations`: + - `CreateTransaction.items` + - `UpdateTransaction.items` + - `PreviewTransactionByAddress.items` + - `PreviewTransactionByCustomer.items` + - `PreviewTransactionByIP.items` +- Transaction and Subscription preview responses now support preview products and prices without IDs (see UPGRADING.md for further details) ### Removed - `get_parameters()` method on request operation classes is now removed or replaced by `to_json()` (see UPGRADING.md for further details) diff --git a/UPGRADING.md b/UPGRADING.md index 5633a067..a7b9c57b 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -62,6 +62,71 @@ All breaking changes prior to v1 will be documented in this file to assist with - `paddle_billing.Resources.Transactions.Operations.PreviewTransactionByCustomer` - `paddle_billing.Resources.Transactions.Operations.PreviewTransactionByIP` +### 2. Transaction and Subscription operation items now allow optional properties to be omitted. + +Transaction and Subscription operation item types have changed to new types that allow optional properties to be omitted. + +- `paddle_billing.Resources.Subscriptions.Operations` `UpdateSubscription` and `PreviewUpdateSubscription` `items` are now list of: + - `paddle_billing.Resources.Subscriptions.Operations.Update.SubscriptionUpdateItem` + - `paddle_billing.Resources.Subscriptions.Operations.Update.SubscriptionUpdateItemWithPrice` +- `paddle_billing.Resources.Subscriptions.Operations` `CreateOneTimeCharge` and `PreviewOneTimeCharge` `items` are now list of: + - `paddle_billing.Resources.Subscriptions.Operations.Charge.SubscriptionChargeItem` + - `paddle_billing.Resources.Subscriptions.Operations.Charge.SubscriptionChargeItemWithPrice` +- `paddle_billing.Resources.Transactions.Operations.CreateTransaction.items` is now list of: + - `paddle_billing.Resources.Transactions.Operations.Create.TransactionCreateItem` + - `paddle_billing.Resources.Transactions.Operations.Create.TransactionCreateItemWithPrice` +- `paddle_billing.Resources.Transactions.Operations.UpdateTransaction.items` is now list of: + - `paddle_billing.Resources.Transactions.Operations.Update.TransactionUpdateItem` + - `paddle_billing.Resources.Transactions.Operations.Update.TransactionUpdateItemWithPrice` +- `paddle_billing.Resources.Transactions.Operations` `PreviewTransactionByAddress`, `PreviewTransactionByCustomer` and `PreviewTransactionByIP` `items` are now list of: + - `paddle_billing.Resources.Transactions.Operations.Preview.TransactionItemPreviewWithNonCatalogPrice` + - `paddle_billing.Resources.Transactions.Operations.Preview.TransactionItemPreviewWithPriceId` + + +The following classes have been removed: +- `paddle_billing.Entities.Subscriptions`: + - `SubscriptionItems` + - replaced by `paddle_billing.Resources.Subscriptions.Operations.Update.SubscriptionUpdateItem` + - `SubscriptionItemsWithPrice` + - replaced by `paddle_billing.Resources.Subscriptions.Operations.Update.SubscriptionUpdateItemWithPrice` + - `SubscriptionNonCatalogPrice` + - replaced by `paddle_billing.Resources.Subscriptions.Operations.Price.SubscriptionNonCatalogPrice` + - `SubscriptionNonCatalogPriceWithProduct` + - replaced by `paddle_billing.Resources.Subscriptions.Operations.Price.SubscriptionNonCatalogPriceWithProduct` + - `SubscriptionNonCatalogProduct` + - replaced by `paddle_billing.Resources.Subscriptions.Operations.Price.SubscriptionNonCatalogProduct` +- `paddle_billing.Entities.Transactions`: + - `TransactionCreateItem` + - replaced by: + - `paddle_billing.Resources.Transactions.Operations.Create.TransactionCreateItem` (for create) + - `paddle_billing.Resources.Transactions.Operations.Update.TransactionUpdateItem` (for update) + - `TransactionCreateItemWithPrice` + - replaced by: + - `paddle_billing.Resources.Transactions.Operations.Create.TransactionCreateItemWithPrice` (for create) + - `paddle_billing.Resources.Transactions.Operations.Update.TransactionUpdateItemWithPrice` (for update) + - `TransactionNonCatalogPrice` + - replaced by `paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPrice` + - `TransactionNonCatalogPriceWithProduct` + - replaced by `paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPriceWithProduct` + - `TransactionNonCatalogProduct` + - replaced by `paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogProduct` + - `TransactionItemPreviewWithNonCatalogPrice` + - replaced by `paddle_billing.Resources.Transactions.Operations.Preview.TransactionItemPreviewWithNonCatalogPrice` + - `TransactionItemPreviewWithPriceId` + - replaced by `paddle_billing.Resources.Transactions.Operations.Preview.TransactionItemPreviewWithPriceId` + +### 3. Transaction and Subscription preview responses now support preview products and prices without IDs + +- `SubscriptionPreview.immediate_transaction.details.line_items[].price_id` can now be `None` +- `SubscriptionPreview.immediate_transaction.details.line_items[].product` is now `paddle_billing.Entities.Shared.TransactionPreviewProduct` +- `SubscriptionPreview.next_transaction.details.line_items[].price_id` can now be `None` +- `SubscriptionPreview.next_transaction.details.line_items[].product` is now `paddle_billing.Entities.Shared.TransactionPreviewProduct` +- `SubscriptionPreview.recurring_transaction_details.line_items[].price_id` can now be `None` +- `SubscriptionPreview.recurring_transaction_details.line_items[].product` is now `paddle_billing.Entities.Shared.TransactionPreviewProduct` +- `TransactionPreview.items[].price` is now `TransactionPreviewPrice` +- `TransactionPreview.details.line_items[].price_id` can now be `None` +- `TransactionPreview.details.line_items[].product` is now `paddle_billing.Entities.Shared.TransactionPreviewProduct` + ## v0.3.0 ### 1. `AvailablePaymentMethods` has been replaced by `PaymentMethodType`. diff --git a/paddle_billing/Entities/Price.py b/paddle_billing/Entities/Price.py index 00b4b322..1fcddd62 100644 --- a/paddle_billing/Entities/Price.py +++ b/paddle_billing/Entities/Price.py @@ -15,10 +15,6 @@ UnitPriceOverride, ) -from paddle_billing.Logger import get_logger - -log = get_logger() - @dataclass class Price(Entity): diff --git a/paddle_billing/Entities/Shared/TransactionLineItemPreview.py b/paddle_billing/Entities/Shared/TransactionLineItemPreview.py index 665c6dc2..8a18e4eb 100644 --- a/paddle_billing/Entities/Shared/TransactionLineItemPreview.py +++ b/paddle_billing/Entities/Shared/TransactionLineItemPreview.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from paddle_billing.Entities.Product import Product +from paddle_billing.Entities.Shared.TransactionPreviewProduct import TransactionPreviewProduct from paddle_billing.Entities.Shared.Totals import Totals from paddle_billing.Entities.Shared.UnitTotals import UnitTotals from paddle_billing.Entities.Shared.Proration import Proration @@ -9,22 +9,22 @@ @dataclass class TransactionLineItemPreview: - price_id: str + price_id: str | None quantity: int tax_rate: str unit_totals: UnitTotals totals: Totals - product: Product + product: TransactionPreviewProduct proration: Proration | None @staticmethod def from_dict(data: dict) -> TransactionLineItemPreview: return TransactionLineItemPreview( - price_id=data["price_id"], + price_id=data.get("price_id"), quantity=data["quantity"], tax_rate=data["tax_rate"], unit_totals=UnitTotals.from_dict(data["unit_totals"]), totals=Totals.from_dict(data["totals"]), - product=Product.from_dict(data["product"]), + product=TransactionPreviewProduct.from_dict(data["product"]), proration=Proration.from_dict(data["proration"]) if data.get("proration") else None, ) diff --git a/paddle_billing/Entities/Shared/TransactionPreviewProduct.py b/paddle_billing/Entities/Shared/TransactionPreviewProduct.py new file mode 100644 index 00000000..c2a66532 --- /dev/null +++ b/paddle_billing/Entities/Shared/TransactionPreviewProduct.py @@ -0,0 +1,37 @@ +from __future__ import annotations +from dataclasses import dataclass +from datetime import datetime + +from paddle_billing.Entities.Entity import Entity +from paddle_billing.Entities.Shared import CatalogType, CustomData, ImportMeta, Status, TaxCategory + + +@dataclass +class TransactionPreviewProduct(Entity): + id: str | None + name: str + description: str | None + tax_category: TaxCategory + image_url: str | None + status: Status + created_at: datetime + updated_at: datetime + type: CatalogType | None = None + custom_data: CustomData | None = None + import_meta: ImportMeta | None = None + + @staticmethod + def from_dict(data: dict) -> TransactionPreviewProduct: + return TransactionPreviewProduct( + id=data.get("id"), + name=data["name"], + description=data.get("description"), + tax_category=TaxCategory(data["tax_category"]), + image_url=data.get("image_url"), + status=Status(data["status"]), + created_at=datetime.fromisoformat(data["created_at"]), + updated_at=datetime.fromisoformat(data["updated_at"]), + type=CatalogType(data["type"]) if data.get("type") else None, + custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, + import_meta=ImportMeta.from_dict(data["import_meta"]) if data.get("import_meta") else None, + ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionAdjustmentItem.py b/paddle_billing/Entities/Subscriptions/SubscriptionAdjustmentItem.py index 073be153..c1b270b5 100644 --- a/paddle_billing/Entities/Subscriptions/SubscriptionAdjustmentItem.py +++ b/paddle_billing/Entities/Subscriptions/SubscriptionAdjustmentItem.py @@ -9,7 +9,7 @@ class SubscriptionAdjustmentItem: item_id: str type: AdjustmentType amount: str | None - proration: Proration + proration: Proration | None totals: AdjustmentItemTotals @staticmethod @@ -18,6 +18,6 @@ def from_dict(data: dict) -> SubscriptionAdjustmentItem: item_id=data["item_id"], type=AdjustmentType(data["type"]), amount=data.get("amount"), - proration=Proration.from_dict(data["proration"]), + proration=Proration.from_dict(data["proration"]) if data.get("proration") else None, totals=AdjustmentItemTotals.from_dict(data["totals"]), ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionItems.py b/paddle_billing/Entities/Subscriptions/SubscriptionItems.py deleted file mode 100644 index 86e24807..00000000 --- a/paddle_billing/Entities/Subscriptions/SubscriptionItems.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - - -@dataclass -class SubscriptionItems: - price_id: str - quantity: int - - @staticmethod - def from_dict(data: dict) -> SubscriptionItems: - return SubscriptionItems( - price_id=data["price_id"], - quantity=data["quantity"], - ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionItemsWithPrice.py b/paddle_billing/Entities/Subscriptions/SubscriptionItemsWithPrice.py deleted file mode 100644 index 78942b7e..00000000 --- a/paddle_billing/Entities/Subscriptions/SubscriptionItemsWithPrice.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Subscriptions.SubscriptionNonCatalogPrice import SubscriptionNonCatalogPrice -from paddle_billing.Entities.Subscriptions.SubscriptionNonCatalogPriceWithProduct import ( - SubscriptionNonCatalogPriceWithProduct, -) - - -@dataclass -class SubscriptionItemsWithPrice: - price: SubscriptionNonCatalogPrice | SubscriptionNonCatalogPriceWithProduct - quantity: int - - @staticmethod - def from_dict(data: dict) -> SubscriptionItemsWithPrice: - return SubscriptionItemsWithPrice( - price=data["price"], - quantity=data["quantity"], - ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogPrice.py b/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogPrice.py deleted file mode 100644 index 871cfab5..00000000 --- a/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogPrice.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Shared import CustomData, Duration, Money, PriceQuantity, TaxMode, UnitPriceOverride - - -@dataclass -class SubscriptionNonCatalogPrice: - description: str - name: str | None - product_id: str - tax_mode: TaxMode - unit_price: Money - unit_price_overrides: list[UnitPriceOverride] - quantity: PriceQuantity - custom_data: CustomData | None - billing_cycle: Duration | None - trial_period: Duration | None - - @staticmethod - def from_dict(data: dict) -> SubscriptionNonCatalogPrice: - return SubscriptionNonCatalogPrice( - description=data["description"], - name=data.get("name"), - product_id=data["product_id"], - tax_mode=TaxMode(data["tax_mode"]), - unit_price=Money.from_dict(data["unit_price"]), - unit_price_overrides=[UnitPriceOverride.from_dict(override) for override in data["unit_price_overrides"]], - quantity=PriceQuantity.from_dict(data["quantity"]), - custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, - billing_cycle=Duration.from_dict(data["billing_cycle"]) if data.get("billing_cycle") else None, - trial_period=Duration.from_dict(data["trial_period"]) if data.get("trial_period") else None, - ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogPriceWithProduct.py b/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogPriceWithProduct.py deleted file mode 100644 index 4d0be2e1..00000000 --- a/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogPriceWithProduct.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Shared import CustomData, Duration, Money, PriceQuantity, TaxMode, UnitPriceOverride - -from paddle_billing.Entities.Subscriptions.SubscriptionNonCatalogProduct import SubscriptionNonCatalogProduct - - -@dataclass -class SubscriptionNonCatalogPriceWithProduct: - description: str - name: str | None - product: SubscriptionNonCatalogProduct - tax_mode: TaxMode - unit_price: Money - unit_price_overrides: list[UnitPriceOverride] - quantity: PriceQuantity - custom_data: CustomData | None - billing_cycle: Duration | None - trial_period: Duration | None - - @staticmethod - def from_dict(data: dict) -> SubscriptionNonCatalogPriceWithProduct: - return SubscriptionNonCatalogPriceWithProduct( - description=data["description"], - name=data.get("name"), - product=SubscriptionNonCatalogProduct.from_dict(data["product"]), - tax_mode=TaxMode(data["tax_mode"]), - unit_price=Money.from_dict(data["unit_price"]), - unit_price_overrides=[UnitPriceOverride.from_dict(override) for override in data["unit_price_overrides"]], - quantity=PriceQuantity.from_dict(data["quantity"]), - custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, - billing_cycle=Duration.from_dict(data["billing_cycle"]) if data.get("billing_cycle") else None, - trial_period=Duration.from_dict(data["trial_period"]) if data.get("trial_period") else None, - ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogProduct.py b/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogProduct.py deleted file mode 100644 index bea6788f..00000000 --- a/paddle_billing/Entities/Subscriptions/SubscriptionNonCatalogProduct.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Shared import CatalogType, CustomData, TaxCategory - - -@dataclass -class SubscriptionNonCatalogProduct: - name: str - description: str | None - type: CatalogType | None - tax_category: TaxCategory - image_url: str | None - custom_data: CustomData | None - - @staticmethod - def from_dict(data: dict) -> SubscriptionNonCatalogProduct: - return SubscriptionNonCatalogProduct( - name=data["name"], - description=data.get("description"), - tax_category=TaxCategory(data["tax_category"]), - image_url=data.get("image_url"), - type=CatalogType(data["type"]) if data.get("type") else None, - custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, - ) diff --git a/paddle_billing/Entities/Subscriptions/SubscriptionUpdateItem.py b/paddle_billing/Entities/Subscriptions/SubscriptionUpdateItem.py deleted file mode 100644 index ac93058c..00000000 --- a/paddle_billing/Entities/Subscriptions/SubscriptionUpdateItem.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - - -@dataclass -class SubscriptionUpdateItem: - price_id: str - quantity: int - - @staticmethod - def from_dict(data: dict) -> SubscriptionUpdateItem: - return SubscriptionUpdateItem( - price_id=data["price_id"], - quantity=data["quantity"], - ) diff --git a/paddle_billing/Entities/Subscriptions/__init__.py b/paddle_billing/Entities/Subscriptions/__init__.py index 752c56af..a10608f1 100644 --- a/paddle_billing/Entities/Subscriptions/__init__.py +++ b/paddle_billing/Entities/Subscriptions/__init__.py @@ -6,15 +6,8 @@ from paddle_billing.Entities.Subscriptions.SubscriptionEffectiveFrom import SubscriptionEffectiveFrom from paddle_billing.Entities.Subscriptions.SubscriptionItem import SubscriptionItem from paddle_billing.Entities.Subscriptions.SubscriptionItemStatus import SubscriptionItemStatus -from paddle_billing.Entities.Subscriptions.SubscriptionItems import SubscriptionItems -from paddle_billing.Entities.Subscriptions.SubscriptionItemsWithPrice import SubscriptionItemsWithPrice from paddle_billing.Entities.Subscriptions.SubscriptionManagementUrls import SubscriptionManagementUrls from paddle_billing.Entities.Subscriptions.SubscriptionNextTransaction import SubscriptionNextTransaction -from paddle_billing.Entities.Subscriptions.SubscriptionNonCatalogPrice import SubscriptionNonCatalogPrice -from paddle_billing.Entities.Subscriptions.SubscriptionNonCatalogPriceWithProduct import ( - SubscriptionNonCatalogPriceWithProduct, -) -from paddle_billing.Entities.Subscriptions.SubscriptionNonCatalogProduct import SubscriptionNonCatalogProduct from paddle_billing.Entities.Subscriptions.SubscriptionOnPaymentFailure import SubscriptionOnPaymentFailure from paddle_billing.Entities.Subscriptions.SubscriptionPreviewSubscriptionUpdateSummary import ( SubscriptionPreviewSubscriptionUpdateSummary, @@ -26,4 +19,3 @@ from paddle_billing.Entities.Subscriptions.SubscriptionScheduledChange import SubscriptionScheduledChange from paddle_billing.Entities.Subscriptions.SubscriptionScheduledChangeAction import SubscriptionScheduledChangeAction from paddle_billing.Entities.Subscriptions.SubscriptionStatus import SubscriptionStatus -from paddle_billing.Entities.Subscriptions.SubscriptionUpdateItem import SubscriptionUpdateItem diff --git a/paddle_billing/Entities/Transactions/TransactionCreateItem.py b/paddle_billing/Entities/Transactions/TransactionCreateItem.py deleted file mode 100644 index e08ac66f..00000000 --- a/paddle_billing/Entities/Transactions/TransactionCreateItem.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - - -@dataclass -class TransactionCreateItem: - price_id: str - quantity: int - - @staticmethod - def from_dict(data: dict) -> TransactionCreateItem: - return TransactionCreateItem( - price_id=data["price_id"], - quantity=data["quantity"], - ) diff --git a/paddle_billing/Entities/Transactions/TransactionItem.py b/paddle_billing/Entities/Transactions/TransactionItem.py index bec5ef90..29497cd8 100644 --- a/paddle_billing/Entities/Transactions/TransactionItem.py +++ b/paddle_billing/Entities/Transactions/TransactionItem.py @@ -8,7 +8,6 @@ @dataclass class TransactionItem: - price_id: str | None price: Price quantity: int proration: Proration | None @@ -16,7 +15,6 @@ class TransactionItem: @staticmethod def from_dict(data: dict) -> TransactionItem: return TransactionItem( - price_id=data.get("price_id"), price=Price.from_dict(data["price"]), quantity=data["quantity"], proration=Proration.from_dict(data["proration"]) if data.get("proration") else None, diff --git a/paddle_billing/Entities/Transactions/TransactionItemPreviewWithNonCatalogPrice.py b/paddle_billing/Entities/Transactions/TransactionItemPreviewWithNonCatalogPrice.py deleted file mode 100644 index f5c8b62d..00000000 --- a/paddle_billing/Entities/Transactions/TransactionItemPreviewWithNonCatalogPrice.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Transactions.TransactionNonCatalogPrice import TransactionNonCatalogPrice -from paddle_billing.Entities.Transactions.TransactionNonCatalogPriceWithProduct import ( - TransactionNonCatalogPriceWithProduct, -) - - -@dataclass -class TransactionItemPreviewWithNonCatalogPrice: - price: TransactionNonCatalogPrice | TransactionNonCatalogPriceWithProduct - quantity: int - include_in_totals: bool | None - - @staticmethod - def from_dict(data: dict) -> TransactionItemPreviewWithNonCatalogPrice: - return TransactionItemPreviewWithNonCatalogPrice( - price=TransactionItemPreviewWithNonCatalogPrice._create_price(data["price"]), - quantity=data["quantity"], - include_in_totals=data.get("include_in_totals"), - ) - - @staticmethod - def _create_price(data: dict) -> TransactionNonCatalogPrice | TransactionNonCatalogPriceWithProduct: - if data.get("product") is not None: - return TransactionNonCatalogPriceWithProduct.from_dict(data) - - return TransactionNonCatalogPrice.from_dict(data) diff --git a/paddle_billing/Entities/Transactions/TransactionItemPreviewWithPrice.py b/paddle_billing/Entities/Transactions/TransactionItemPreviewWithPrice.py index 571ff79a..902a4ee2 100644 --- a/paddle_billing/Entities/Transactions/TransactionItemPreviewWithPrice.py +++ b/paddle_billing/Entities/Transactions/TransactionItemPreviewWithPrice.py @@ -1,14 +1,14 @@ from __future__ import annotations from dataclasses import dataclass -from paddle_billing.Entities.Price import Price +from paddle_billing.Entities.Transactions.TransactionPreviewPrice import TransactionPreviewPrice from paddle_billing.Entities.Shared.Proration import Proration @dataclass class TransactionItemPreviewWithPrice: - price: Price + price: TransactionPreviewPrice quantity: int include_in_totals: bool proration: Proration | None @@ -16,7 +16,7 @@ class TransactionItemPreviewWithPrice: @staticmethod def from_dict(data: dict) -> TransactionItemPreviewWithPrice: return TransactionItemPreviewWithPrice( - price=Price.from_dict(data["price"]), + price=TransactionPreviewPrice.from_dict(data["price"]), quantity=data["quantity"], include_in_totals=data["include_in_totals"], proration=Proration.from_dict(data["proration"]) if data.get("proration") else None, diff --git a/paddle_billing/Entities/Transactions/TransactionItemPreviewWithPriceId.py b/paddle_billing/Entities/Transactions/TransactionItemPreviewWithPriceId.py deleted file mode 100644 index 567e1079..00000000 --- a/paddle_billing/Entities/Transactions/TransactionItemPreviewWithPriceId.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - - -@dataclass -class TransactionItemPreviewWithPriceId: - price_id: str - quantity: int - include_in_totals: bool | None - - @staticmethod - def from_dict(data: dict) -> TransactionItemPreviewWithPriceId: - return TransactionItemPreviewWithPriceId( - price_id=data["price_id"], - quantity=data["quantity"], - include_in_totals=data.get("include_in_totals"), - ) diff --git a/paddle_billing/Entities/Transactions/TransactionNonCatalogPrice.py b/paddle_billing/Entities/Transactions/TransactionNonCatalogPrice.py deleted file mode 100644 index c5ab77a1..00000000 --- a/paddle_billing/Entities/Transactions/TransactionNonCatalogPrice.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Shared.Duration import Duration -from paddle_billing.Entities.Shared.TaxMode import TaxMode -from paddle_billing.Entities.Shared.Money import Money -from paddle_billing.Entities.Shared.PriceQuantity import PriceQuantity -from paddle_billing.Entities.Shared.CustomData import CustomData -from paddle_billing.Entities.Shared.UnitPriceOverride import UnitPriceOverride - - -@dataclass -class TransactionNonCatalogPrice: - description: str - name: str | None - billing_cycle: Duration | None - trial_period: Duration | None - custom_data: CustomData | None - tax_mode: TaxMode - unit_price: Money - unit_price_overrides: list[UnitPriceOverride] - quantity: PriceQuantity - product_id: str - - @staticmethod - def from_dict(data: dict) -> TransactionNonCatalogPrice: - return TransactionNonCatalogPrice( - description=data["description"], - name=data.get("name"), - tax_mode=data["tax_mode"], - unit_price=Money.from_dict(data["unit_price"]), - quantity=PriceQuantity.from_dict(data["quantity"]), - product_id=data["product_id"], - unit_price_overrides=[UnitPriceOverride.from_dict(item) for item in data["unit_price_overrides"]], - billing_cycle=Duration.from_dict(data["billing_cycle"]) if data.get("billing_cycle") else None, - trial_period=Duration.from_dict(data["trial_period"]) if data.get("trial_period") else None, - custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, - ) diff --git a/paddle_billing/Entities/Transactions/TransactionNonCatalogPriceWithProduct.py b/paddle_billing/Entities/Transactions/TransactionNonCatalogPriceWithProduct.py deleted file mode 100644 index 4b37c413..00000000 --- a/paddle_billing/Entities/Transactions/TransactionNonCatalogPriceWithProduct.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Shared.CustomData import CustomData -from paddle_billing.Entities.Shared.Money import Money -from paddle_billing.Entities.Shared.PriceQuantity import PriceQuantity -from paddle_billing.Entities.Shared.TaxMode import TaxMode -from paddle_billing.Entities.Shared.Duration import Duration -from paddle_billing.Entities.Shared.UnitPriceOverride import UnitPriceOverride - -from paddle_billing.Entities.Transactions.TransactionNonCatalogProduct import TransactionNonCatalogProduct - - -@dataclass -class TransactionNonCatalogPriceWithProduct: - description: str - name: str | None - billing_cycle: Duration | None - trial_period: Duration | None - custom_data: CustomData | None - tax_mode: TaxMode - unit_price: Money - unit_price_overrides: list[UnitPriceOverride] - quantity: PriceQuantity - product: TransactionNonCatalogProduct - - @staticmethod - def from_dict(data: dict) -> TransactionNonCatalogPriceWithProduct: - return TransactionNonCatalogPriceWithProduct( - description=data["description"], - name=data.get("name"), - tax_mode=data["tax_mode"], - unit_price=Money.from_dict(data["unit_price"]), - quantity=PriceQuantity.from_dict(data["quantity"]), - product=TransactionNonCatalogProduct.from_dict(data["product"]), - unit_price_overrides=[UnitPriceOverride.from_dict(item) for item in data["unit_price_overrides"]], - billing_cycle=Duration.from_dict(data["billing_cycle"]) if data.get("billing_cycle") else None, - trial_period=Duration.from_dict(data["trial_period"]) if data.get("trial_period") else None, - custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, - ) diff --git a/paddle_billing/Entities/Transactions/TransactionNonCatalogProduct.py b/paddle_billing/Entities/Transactions/TransactionNonCatalogProduct.py deleted file mode 100644 index 259bc4e0..00000000 --- a/paddle_billing/Entities/Transactions/TransactionNonCatalogProduct.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass - -from paddle_billing.Entities.Shared.CustomData import CustomData -from paddle_billing.Entities.Shared.TaxCategory import TaxCategory - - -@dataclass -class TransactionNonCatalogProduct: - name: str - tax_category: TaxCategory - description: str | None - image_url: str | None - custom_data: CustomData | None - - @staticmethod - def from_dict(data: dict) -> TransactionNonCatalogProduct: - return TransactionNonCatalogProduct( - name=data["name"], - description=data.get("description"), - image_url=data.get("image_url"), - tax_category=TaxCategory(data["tax_category"]) if data.get("tax_category") else None, - custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, - ) diff --git a/paddle_billing/Entities/Transactions/TransactionPreviewPrice.py b/paddle_billing/Entities/Transactions/TransactionPreviewPrice.py new file mode 100644 index 00000000..bff2ffc3 --- /dev/null +++ b/paddle_billing/Entities/Transactions/TransactionPreviewPrice.py @@ -0,0 +1,59 @@ +from __future__ import annotations +from dataclasses import dataclass +from datetime import datetime + +from paddle_billing.Entities.Entity import Entity +from paddle_billing.Entities.Shared import ( + CatalogType, + CustomData, + Duration, + ImportMeta, + Money, + PriceQuantity, + Status, + TaxMode, + UnitPriceOverride, +) + + +@dataclass +class TransactionPreviewPrice(Entity): + id: str | None + product_id: str | None + name: str | None + description: str + type: CatalogType + billing_cycle: Duration | None + trial_period: Duration | None + tax_mode: TaxMode + unit_price: Money + unit_price_overrides: list[UnitPriceOverride] + quantity: PriceQuantity + status: Status + custom_data: CustomData | None + import_meta: ImportMeta | None + created_at: datetime + updated_at: datetime + + @staticmethod + def from_dict(data: dict) -> TransactionPreviewPrice: + return TransactionPreviewPrice( + id=data.get("id"), + product_id=data.get("product_id"), + name=data.get("name"), + description=data["description"], + unit_price=Money.from_dict(data["unit_price"]), + quantity=PriceQuantity.from_dict(data["quantity"]), + status=Status(data["status"]), + tax_mode=TaxMode(data.get("tax_mode")), + created_at=datetime.fromisoformat(data["created_at"]), + updated_at=datetime.fromisoformat(data["updated_at"]), + unit_price_overrides=[ + UnitPriceOverride.from_dict(override) for override in data.get("unit_price_overrides", []) + ], + type=CatalogType(data.get("type")) if data.get("type") else None, + billing_cycle=Duration.from_dict(data["billing_cycle"]) if data.get("billing_cycle") else None, + trial_period=Duration.from_dict(data["trial_period"]) if data.get("trial_period") else None, + custom_data=CustomData(data["custom_data"]) if data.get("custom_data") else None, + import_meta=ImportMeta.from_dict(data["import_meta"]) if data.get("import_meta") else None, + ) diff --git a/paddle_billing/Entities/Transactions/__init__.py b/paddle_billing/Entities/Transactions/__init__.py index e20668d0..75311524 100644 --- a/paddle_billing/Entities/Transactions/__init__.py +++ b/paddle_billing/Entities/Transactions/__init__.py @@ -1,19 +1,7 @@ from paddle_billing.Entities.Transactions.TransactionAdjustmentsTotals import TransactionAdjustmentsTotals from paddle_billing.Entities.Transactions.TransactionBreakdown import TransactionBreakdown from paddle_billing.Entities.Transactions.TransactionCardType import TransactionCardType -from paddle_billing.Entities.Transactions.TransactionCreateItem import TransactionCreateItem -from paddle_billing.Entities.Transactions.TransactionCreateItemWithPrice import TransactionCreateItemWithPrice -from paddle_billing.Entities.Transactions.TransactionItemPreviewWithNonCatalogPrice import ( - TransactionItemPreviewWithNonCatalogPrice, -) -from paddle_billing.Entities.Transactions.TransactionItemPreviewWithPriceId import TransactionItemPreviewWithPriceId -from paddle_billing.Entities.Transactions.TransactionNonCatalogPrice import TransactionNonCatalogPrice -from paddle_billing.Entities.Transactions.TransactionNonCatalogPriceWithProduct import ( - TransactionNonCatalogPriceWithProduct, -) -from paddle_billing.Entities.Transactions.TransactionNonCatalogProduct import TransactionNonCatalogProduct from paddle_billing.Entities.Shared.TimePeriod import TimePeriod -from paddle_billing.Entities.Transactions.TransactionUpdateTransactionItem import TransactionUpdateTransactionItem # These cause circular imports diff --git a/paddle_billing/Notifications/Entities/Price.py b/paddle_billing/Notifications/Entities/Price.py index 4b432525..e9e41cf3 100644 --- a/paddle_billing/Notifications/Entities/Price.py +++ b/paddle_billing/Notifications/Entities/Price.py @@ -15,10 +15,6 @@ UnitPriceOverride, ) -from paddle_billing.Logger import get_logger - -log = get_logger() - @dataclass class Price(Entity): diff --git a/paddle_billing/Entities/Transactions/TransactionUpdateTransactionItem.py b/paddle_billing/Resources/Subscriptions/Operations/Charge/SubscriptionChargeItem.py similarity index 74% rename from paddle_billing/Entities/Transactions/TransactionUpdateTransactionItem.py rename to paddle_billing/Resources/Subscriptions/Operations/Charge/SubscriptionChargeItem.py index e0cb0b8c..c421c35c 100644 --- a/paddle_billing/Entities/Transactions/TransactionUpdateTransactionItem.py +++ b/paddle_billing/Resources/Subscriptions/Operations/Charge/SubscriptionChargeItem.py @@ -3,6 +3,6 @@ @dataclass -class TransactionUpdateTransactionItem: +class SubscriptionChargeItem: price_id: str quantity: int diff --git a/paddle_billing/Resources/Subscriptions/Operations/Charge/SubscriptionChargeItemWithPrice.py b/paddle_billing/Resources/Subscriptions/Operations/Charge/SubscriptionChargeItemWithPrice.py new file mode 100644 index 00000000..99e96a4c --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Charge/SubscriptionChargeItemWithPrice.py @@ -0,0 +1,13 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Resources.Subscriptions.Operations.Price import ( + SubscriptionNonCatalogPrice, + SubscriptionNonCatalogPriceWithProduct, +) + + +@dataclass +class SubscriptionChargeItemWithPrice: + price: SubscriptionNonCatalogPrice | SubscriptionNonCatalogPriceWithProduct + quantity: int diff --git a/paddle_billing/Resources/Subscriptions/Operations/Charge/__init__.py b/paddle_billing/Resources/Subscriptions/Operations/Charge/__init__.py new file mode 100644 index 00000000..915d9d10 --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Charge/__init__.py @@ -0,0 +1,4 @@ +from paddle_billing.Resources.Subscriptions.Operations.Charge.SubscriptionChargeItem import SubscriptionChargeItem +from paddle_billing.Resources.Subscriptions.Operations.Charge.SubscriptionChargeItemWithPrice import ( + SubscriptionChargeItemWithPrice, +) diff --git a/paddle_billing/Resources/Subscriptions/Operations/CreateOneTimeCharge.py b/paddle_billing/Resources/Subscriptions/Operations/CreateOneTimeCharge.py index ebe9b46d..dda9543e 100644 --- a/paddle_billing/Resources/Subscriptions/Operations/CreateOneTimeCharge.py +++ b/paddle_billing/Resources/Subscriptions/Operations/CreateOneTimeCharge.py @@ -5,14 +5,17 @@ from paddle_billing.Entities.Subscriptions import ( SubscriptionEffectiveFrom, - SubscriptionItems, - SubscriptionItemsWithPrice, SubscriptionOnPaymentFailure, ) +from paddle_billing.Resources.Subscriptions.Operations.Charge import ( + SubscriptionChargeItem, + SubscriptionChargeItemWithPrice, +) + @dataclass class CreateOneTimeCharge(Operation): effective_from: SubscriptionEffectiveFrom - items: list[SubscriptionItems | SubscriptionItemsWithPrice] + items: list[SubscriptionChargeItem | SubscriptionChargeItemWithPrice] on_payment_failure: SubscriptionOnPaymentFailure | Undefined = Undefined() diff --git a/paddle_billing/Resources/Subscriptions/Operations/PreviewOneTimeCharge.py b/paddle_billing/Resources/Subscriptions/Operations/PreviewOneTimeCharge.py index 6e3d2b66..fea411b2 100644 --- a/paddle_billing/Resources/Subscriptions/Operations/PreviewOneTimeCharge.py +++ b/paddle_billing/Resources/Subscriptions/Operations/PreviewOneTimeCharge.py @@ -5,14 +5,17 @@ from paddle_billing.Entities.Subscriptions import ( SubscriptionEffectiveFrom, - SubscriptionItems, - SubscriptionItemsWithPrice, SubscriptionOnPaymentFailure, ) +from paddle_billing.Resources.Subscriptions.Operations.Charge import ( + SubscriptionChargeItem, + SubscriptionChargeItemWithPrice, +) + @dataclass class PreviewOneTimeCharge(Operation): effective_from: SubscriptionEffectiveFrom - items: list[SubscriptionItems | SubscriptionItemsWithPrice] + items: list[SubscriptionChargeItem | SubscriptionChargeItemWithPrice] on_payment_failure: SubscriptionOnPaymentFailure | Undefined = Undefined() diff --git a/paddle_billing/Resources/Subscriptions/Operations/PreviewUpdateSubscription.py b/paddle_billing/Resources/Subscriptions/Operations/PreviewUpdateSubscription.py index 35c5b6f2..03e15322 100644 --- a/paddle_billing/Resources/Subscriptions/Operations/PreviewUpdateSubscription.py +++ b/paddle_billing/Resources/Subscriptions/Operations/PreviewUpdateSubscription.py @@ -6,14 +6,14 @@ from paddle_billing.Entities.DateTime import DateTime from paddle_billing.Entities.Shared import CollectionMode, CurrencyCode, CustomData from paddle_billing.Entities.Subscriptions import ( - SubscriptionItems, - SubscriptionItemsWithPrice, SubscriptionOnPaymentFailure, SubscriptionProrationBillingMode, ) from paddle_billing.Resources.Subscriptions.Operations.Update import ( SubscriptionDiscount, + SubscriptionUpdateItem, + SubscriptionUpdateItemWithPrice, UpdateBillingDetails, ) @@ -29,7 +29,7 @@ class PreviewUpdateSubscription(Operation): collection_mode: CollectionMode | Undefined = Undefined() billing_details: UpdateBillingDetails | None | Undefined = Undefined() scheduled_change: None | Undefined = Undefined() - items: list[SubscriptionItems | SubscriptionItemsWithPrice] | Undefined = Undefined() + items: list[SubscriptionUpdateItem | SubscriptionUpdateItemWithPrice] | Undefined = Undefined() custom_data: CustomData | None | Undefined = Undefined() proration_billing_mode: SubscriptionProrationBillingMode | Undefined = Undefined() on_payment_failure: SubscriptionOnPaymentFailure | Undefined = Undefined() diff --git a/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogPrice.py b/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogPrice.py new file mode 100644 index 00000000..589b25b9 --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogPrice.py @@ -0,0 +1,24 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Undefined import Undefined +from paddle_billing.Entities.Shared.CustomData import CustomData +from paddle_billing.Entities.Shared.Money import Money +from paddle_billing.Entities.Shared.PriceQuantity import PriceQuantity +from paddle_billing.Entities.Shared.TaxMode import TaxMode +from paddle_billing.Entities.Shared.Duration import Duration +from paddle_billing.Entities.Shared.UnitPriceOverride import UnitPriceOverride + + +@dataclass +class SubscriptionNonCatalogPrice: + description: str + unit_price: Money + product_id: str + name: str | None | Undefined = Undefined() + billing_cycle: Duration | None | Undefined = Undefined() + trial_period: Duration | None | Undefined = Undefined() + custom_data: CustomData | None | Undefined = Undefined() + tax_mode: TaxMode | Undefined = Undefined() + unit_price_overrides: list[UnitPriceOverride] | Undefined = Undefined() + quantity: PriceQuantity | Undefined = Undefined() diff --git a/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogPriceWithProduct.py b/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogPriceWithProduct.py new file mode 100644 index 00000000..abdde821 --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogPriceWithProduct.py @@ -0,0 +1,28 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Undefined import Undefined +from paddle_billing.Entities.Shared.CustomData import CustomData +from paddle_billing.Entities.Shared.Money import Money +from paddle_billing.Entities.Shared.PriceQuantity import PriceQuantity +from paddle_billing.Entities.Shared.TaxMode import TaxMode +from paddle_billing.Entities.Shared.Duration import Duration +from paddle_billing.Entities.Shared.UnitPriceOverride import UnitPriceOverride + +from paddle_billing.Resources.Subscriptions.Operations.Price import ( + SubscriptionNonCatalogProduct, +) + + +@dataclass +class SubscriptionNonCatalogPriceWithProduct: + description: str + unit_price: Money + product: SubscriptionNonCatalogProduct + name: str | None | Undefined = Undefined() + billing_cycle: Duration | None | Undefined = Undefined() + trial_period: Duration | None | Undefined = Undefined() + custom_data: CustomData | None | Undefined = Undefined() + tax_mode: TaxMode | Undefined = Undefined() + unit_price_overrides: list[UnitPriceOverride] | Undefined = Undefined() + quantity: PriceQuantity | Undefined = Undefined() diff --git a/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogProduct.py b/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogProduct.py new file mode 100644 index 00000000..50b085fd --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Price/SubscriptionNonCatalogProduct.py @@ -0,0 +1,15 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Undefined import Undefined +from paddle_billing.Entities.Shared.CustomData import CustomData +from paddle_billing.Entities.Shared.TaxCategory import TaxCategory + + +@dataclass +class SubscriptionNonCatalogProduct: + name: str + tax_category: TaxCategory + description: str | None | Undefined = Undefined() + image_url: str | None | Undefined = Undefined() + custom_data: CustomData | None | Undefined = Undefined() diff --git a/paddle_billing/Resources/Subscriptions/Operations/Price/__init__.py b/paddle_billing/Resources/Subscriptions/Operations/Price/__init__.py new file mode 100644 index 00000000..808769f1 --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Price/__init__.py @@ -0,0 +1,9 @@ +from paddle_billing.Resources.Subscriptions.Operations.Price.SubscriptionNonCatalogPrice import ( + SubscriptionNonCatalogPrice, +) +from paddle_billing.Resources.Subscriptions.Operations.Price.SubscriptionNonCatalogPriceWithProduct import ( + SubscriptionNonCatalogPriceWithProduct, +) +from paddle_billing.Resources.Subscriptions.Operations.Price.SubscriptionNonCatalogProduct import ( + SubscriptionNonCatalogProduct, +) diff --git a/paddle_billing/Resources/Subscriptions/Operations/Update/SubscriptionUpdateItem.py b/paddle_billing/Resources/Subscriptions/Operations/Update/SubscriptionUpdateItem.py new file mode 100644 index 00000000..dad31e5f --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Update/SubscriptionUpdateItem.py @@ -0,0 +1,9 @@ +from __future__ import annotations +from dataclasses import dataclass +from paddle_billing.Undefined import Undefined + + +@dataclass +class SubscriptionUpdateItem: + price_id: str + quantity: int | Undefined = Undefined() diff --git a/paddle_billing/Resources/Subscriptions/Operations/Update/SubscriptionUpdateItemWithPrice.py b/paddle_billing/Resources/Subscriptions/Operations/Update/SubscriptionUpdateItemWithPrice.py new file mode 100644 index 00000000..d1a9b05f --- /dev/null +++ b/paddle_billing/Resources/Subscriptions/Operations/Update/SubscriptionUpdateItemWithPrice.py @@ -0,0 +1,13 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Resources.Subscriptions.Operations.Price import ( + SubscriptionNonCatalogPrice, + SubscriptionNonCatalogPriceWithProduct, +) + + +@dataclass +class SubscriptionUpdateItemWithPrice: + price: SubscriptionNonCatalogPrice | SubscriptionNonCatalogPriceWithProduct + quantity: int diff --git a/paddle_billing/Resources/Subscriptions/Operations/Update/__init__.py b/paddle_billing/Resources/Subscriptions/Operations/Update/__init__.py index 3033e756..915aa2ef 100644 --- a/paddle_billing/Resources/Subscriptions/Operations/Update/__init__.py +++ b/paddle_billing/Resources/Subscriptions/Operations/Update/__init__.py @@ -1,2 +1,6 @@ from paddle_billing.Resources.Subscriptions.Operations.Update.SubscriptionDiscount import SubscriptionDiscount +from paddle_billing.Resources.Subscriptions.Operations.Update.SubscriptionUpdateItem import SubscriptionUpdateItem +from paddle_billing.Resources.Subscriptions.Operations.Update.SubscriptionUpdateItemWithPrice import ( + SubscriptionUpdateItemWithPrice, +) from paddle_billing.Resources.Subscriptions.Operations.Update.UpdateBillingDetails import UpdateBillingDetails diff --git a/paddle_billing/Resources/Subscriptions/Operations/UpdateSubscription.py b/paddle_billing/Resources/Subscriptions/Operations/UpdateSubscription.py index 2139f6a6..f53d8f27 100644 --- a/paddle_billing/Resources/Subscriptions/Operations/UpdateSubscription.py +++ b/paddle_billing/Resources/Subscriptions/Operations/UpdateSubscription.py @@ -6,14 +6,14 @@ from paddle_billing.Entities.DateTime import DateTime from paddle_billing.Entities.Shared import CollectionMode, CurrencyCode, CustomData from paddle_billing.Entities.Subscriptions import ( - SubscriptionItems, - SubscriptionItemsWithPrice, SubscriptionOnPaymentFailure, SubscriptionProrationBillingMode, ) from paddle_billing.Resources.Subscriptions.Operations.Update import ( SubscriptionDiscount, + SubscriptionUpdateItem, + SubscriptionUpdateItemWithPrice, UpdateBillingDetails, ) @@ -29,7 +29,7 @@ class UpdateSubscription(Operation): collection_mode: CollectionMode | Undefined = Undefined() billing_details: UpdateBillingDetails | None | Undefined = Undefined() scheduled_change: None | Undefined = Undefined() - items: list[SubscriptionItems | SubscriptionItemsWithPrice] | Undefined = Undefined() + items: list[SubscriptionUpdateItem | SubscriptionUpdateItemWithPrice] | Undefined = Undefined() custom_data: CustomData | None | Undefined = Undefined() proration_billing_mode: SubscriptionProrationBillingMode | Undefined = Undefined() on_payment_failure: SubscriptionOnPaymentFailure | Undefined = Undefined() diff --git a/paddle_billing/Resources/Transactions/Operations/Create/TransactionCreateItem.py b/paddle_billing/Resources/Transactions/Operations/Create/TransactionCreateItem.py new file mode 100644 index 00000000..9de14b7f --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Create/TransactionCreateItem.py @@ -0,0 +1,8 @@ +from __future__ import annotations +from dataclasses import dataclass + + +@dataclass +class TransactionCreateItem: + price_id: str + quantity: int diff --git a/paddle_billing/Entities/Transactions/TransactionCreateItemWithPrice.py b/paddle_billing/Resources/Transactions/Operations/Create/TransactionCreateItemWithPrice.py similarity index 53% rename from paddle_billing/Entities/Transactions/TransactionCreateItemWithPrice.py rename to paddle_billing/Resources/Transactions/Operations/Create/TransactionCreateItemWithPrice.py index 78e8a377..3f17b08b 100644 --- a/paddle_billing/Entities/Transactions/TransactionCreateItemWithPrice.py +++ b/paddle_billing/Resources/Transactions/Operations/Create/TransactionCreateItemWithPrice.py @@ -1,8 +1,8 @@ from __future__ import annotations from dataclasses import dataclass -from paddle_billing.Entities.Transactions.TransactionNonCatalogPrice import TransactionNonCatalogPrice -from paddle_billing.Entities.Transactions.TransactionNonCatalogPriceWithProduct import ( +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPrice import TransactionNonCatalogPrice +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPriceWithProduct import ( TransactionNonCatalogPriceWithProduct, ) diff --git a/paddle_billing/Resources/Transactions/Operations/Create/__init__.py b/paddle_billing/Resources/Transactions/Operations/Create/__init__.py index d379e8a1..0dbdce3a 100644 --- a/paddle_billing/Resources/Transactions/Operations/Create/__init__.py +++ b/paddle_billing/Resources/Transactions/Operations/Create/__init__.py @@ -1 +1,5 @@ from paddle_billing.Resources.Transactions.Operations.Create.CreateBillingDetails import CreateBillingDetails +from paddle_billing.Resources.Transactions.Operations.Create.TransactionCreateItem import TransactionCreateItem +from paddle_billing.Resources.Transactions.Operations.Create.TransactionCreateItemWithPrice import ( + TransactionCreateItemWithPrice, +) diff --git a/paddle_billing/Resources/Transactions/Operations/CreateTransaction.py b/paddle_billing/Resources/Transactions/Operations/CreateTransaction.py index 9a996053..d96d5af1 100644 --- a/paddle_billing/Resources/Transactions/Operations/CreateTransaction.py +++ b/paddle_billing/Resources/Transactions/Operations/CreateTransaction.py @@ -10,8 +10,11 @@ TimePeriod, TransactionStatus, ) -from paddle_billing.Entities.Transactions import TransactionCreateItem, TransactionCreateItemWithPrice -from paddle_billing.Resources.Transactions.Operations.Create import CreateBillingDetails +from paddle_billing.Resources.Transactions.Operations.Create import ( + TransactionCreateItem, + TransactionCreateItemWithPrice, + CreateBillingDetails, +) @dataclass diff --git a/paddle_billing/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithNonCatalogPrice.py b/paddle_billing/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithNonCatalogPrice.py new file mode 100644 index 00000000..047b47ee --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithNonCatalogPrice.py @@ -0,0 +1,15 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Undefined import Undefined +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPrice import TransactionNonCatalogPrice +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPriceWithProduct import ( + TransactionNonCatalogPriceWithProduct, +) + + +@dataclass +class TransactionItemPreviewWithNonCatalogPrice: + price: TransactionNonCatalogPrice | TransactionNonCatalogPriceWithProduct + quantity: int + include_in_totals: bool | None | Undefined = Undefined() diff --git a/paddle_billing/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithPriceId.py b/paddle_billing/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithPriceId.py new file mode 100644 index 00000000..993dc49e --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithPriceId.py @@ -0,0 +1,10 @@ +from __future__ import annotations +from dataclasses import dataclass +from paddle_billing.Undefined import Undefined + + +@dataclass +class TransactionItemPreviewWithPriceId: + price_id: str + quantity: int + include_in_totals: bool | None | Undefined = Undefined() diff --git a/paddle_billing/Resources/Transactions/Operations/Preview/__init__.py b/paddle_billing/Resources/Transactions/Operations/Preview/__init__.py new file mode 100644 index 00000000..8db4c712 --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Preview/__init__.py @@ -0,0 +1,6 @@ +from paddle_billing.Resources.Transactions.Operations.Preview.TransactionItemPreviewWithNonCatalogPrice import ( + TransactionItemPreviewWithNonCatalogPrice, +) +from paddle_billing.Resources.Transactions.Operations.Preview.TransactionItemPreviewWithPriceId import ( + TransactionItemPreviewWithPriceId, +) diff --git a/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByAddress.py b/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByAddress.py index a1cffe14..2571add9 100644 --- a/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByAddress.py +++ b/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByAddress.py @@ -3,7 +3,7 @@ from paddle_billing.Operation import Operation from paddle_billing.Undefined import Undefined from paddle_billing.Entities.Shared import AddressPreview, CurrencyCode -from paddle_billing.Entities.Transactions import ( +from paddle_billing.Resources.Transactions.Operations.Preview import ( TransactionItemPreviewWithPriceId, TransactionItemPreviewWithNonCatalogPrice, ) diff --git a/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByCustomer.py b/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByCustomer.py index ad02b023..2d0aefcc 100644 --- a/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByCustomer.py +++ b/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByCustomer.py @@ -3,7 +3,7 @@ from paddle_billing.Operation import Operation from paddle_billing.Undefined import Undefined from paddle_billing.Entities.Shared import CurrencyCode -from paddle_billing.Entities.Transactions import ( +from paddle_billing.Resources.Transactions.Operations.Preview import ( TransactionItemPreviewWithPriceId, TransactionItemPreviewWithNonCatalogPrice, ) diff --git a/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByIP.py b/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByIP.py index 61e00687..a2d0853e 100644 --- a/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByIP.py +++ b/paddle_billing/Resources/Transactions/Operations/PreviewTransactionByIP.py @@ -3,7 +3,7 @@ from paddle_billing.Operation import Operation from paddle_billing.Undefined import Undefined from paddle_billing.Entities.Shared import CurrencyCode -from paddle_billing.Entities.Transactions import ( +from paddle_billing.Resources.Transactions.Operations.Preview import ( TransactionItemPreviewWithPriceId, TransactionItemPreviewWithNonCatalogPrice, ) diff --git a/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogPrice.py b/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogPrice.py new file mode 100644 index 00000000..d1fbc5fa --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogPrice.py @@ -0,0 +1,24 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Undefined import Undefined +from paddle_billing.Entities.Shared.Duration import Duration +from paddle_billing.Entities.Shared.TaxMode import TaxMode +from paddle_billing.Entities.Shared.Money import Money +from paddle_billing.Entities.Shared.PriceQuantity import PriceQuantity +from paddle_billing.Entities.Shared.CustomData import CustomData +from paddle_billing.Entities.Shared.UnitPriceOverride import UnitPriceOverride + + +@dataclass +class TransactionNonCatalogPrice: + description: str + unit_price: Money + product_id: str + name: str | None | Undefined = Undefined() + billing_cycle: Duration | None | Undefined = Undefined() + trial_period: Duration | None | Undefined = Undefined() + custom_data: CustomData | None | Undefined = Undefined() + tax_mode: TaxMode | Undefined = Undefined() + unit_price_overrides: list[UnitPriceOverride] | Undefined = Undefined() + quantity: PriceQuantity | Undefined = Undefined() diff --git a/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogPriceWithProduct.py b/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogPriceWithProduct.py new file mode 100644 index 00000000..e942bb2a --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogPriceWithProduct.py @@ -0,0 +1,28 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Undefined import Undefined +from paddle_billing.Entities.Shared.CustomData import CustomData +from paddle_billing.Entities.Shared.Money import Money +from paddle_billing.Entities.Shared.PriceQuantity import PriceQuantity +from paddle_billing.Entities.Shared.TaxMode import TaxMode +from paddle_billing.Entities.Shared.Duration import Duration +from paddle_billing.Entities.Shared.UnitPriceOverride import UnitPriceOverride + +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogProduct import ( + TransactionNonCatalogProduct, +) + + +@dataclass +class TransactionNonCatalogPriceWithProduct: + description: str + unit_price: Money + product: TransactionNonCatalogProduct + name: str | None | Undefined = Undefined() + billing_cycle: Duration | None | Undefined = Undefined() + trial_period: Duration | None | Undefined = Undefined() + custom_data: CustomData | None | Undefined = Undefined() + tax_mode: TaxMode | Undefined = Undefined() + unit_price_overrides: list[UnitPriceOverride] | Undefined = Undefined() + quantity: PriceQuantity | Undefined = Undefined() diff --git a/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogProduct.py b/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogProduct.py new file mode 100644 index 00000000..65216d62 --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Price/TransactionNonCatalogProduct.py @@ -0,0 +1,15 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Undefined import Undefined +from paddle_billing.Entities.Shared.CustomData import CustomData +from paddle_billing.Entities.Shared.TaxCategory import TaxCategory + + +@dataclass +class TransactionNonCatalogProduct: + name: str + tax_category: TaxCategory + description: str | None | Undefined = Undefined() + image_url: str | None | Undefined = Undefined() + custom_data: CustomData | None | Undefined = Undefined() diff --git a/paddle_billing/Resources/Transactions/Operations/Price/__init__.py b/paddle_billing/Resources/Transactions/Operations/Price/__init__.py new file mode 100644 index 00000000..508f7426 --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Price/__init__.py @@ -0,0 +1,7 @@ +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPrice import TransactionNonCatalogPrice +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPriceWithProduct import ( + TransactionNonCatalogPriceWithProduct, +) +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogProduct import ( + TransactionNonCatalogProduct, +) diff --git a/paddle_billing/Resources/Transactions/Operations/Update/TransactionUpdateItem.py b/paddle_billing/Resources/Transactions/Operations/Update/TransactionUpdateItem.py new file mode 100644 index 00000000..2c761c1a --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Update/TransactionUpdateItem.py @@ -0,0 +1,8 @@ +from __future__ import annotations +from dataclasses import dataclass + + +@dataclass +class TransactionUpdateItem: + price_id: str + quantity: int diff --git a/paddle_billing/Resources/Transactions/Operations/Update/TransactionUpdateItemWithPrice.py b/paddle_billing/Resources/Transactions/Operations/Update/TransactionUpdateItemWithPrice.py new file mode 100644 index 00000000..ac5fb77d --- /dev/null +++ b/paddle_billing/Resources/Transactions/Operations/Update/TransactionUpdateItemWithPrice.py @@ -0,0 +1,13 @@ +from __future__ import annotations +from dataclasses import dataclass + +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPrice import TransactionNonCatalogPrice +from paddle_billing.Resources.Transactions.Operations.Price.TransactionNonCatalogPriceWithProduct import ( + TransactionNonCatalogPriceWithProduct, +) + + +@dataclass +class TransactionUpdateItemWithPrice: + price: TransactionNonCatalogPrice | TransactionNonCatalogPriceWithProduct + quantity: int diff --git a/paddle_billing/Resources/Transactions/Operations/Update/__init__.py b/paddle_billing/Resources/Transactions/Operations/Update/__init__.py index 37aec216..af2d6381 100644 --- a/paddle_billing/Resources/Transactions/Operations/Update/__init__.py +++ b/paddle_billing/Resources/Transactions/Operations/Update/__init__.py @@ -1 +1,5 @@ from paddle_billing.Resources.Transactions.Operations.Update.UpdateBillingDetails import UpdateBillingDetails +from paddle_billing.Resources.Transactions.Operations.Update.TransactionUpdateItem import TransactionUpdateItem +from paddle_billing.Resources.Transactions.Operations.Update.TransactionUpdateItemWithPrice import ( + TransactionUpdateItemWithPrice, +) diff --git a/paddle_billing/Resources/Transactions/Operations/UpdateTransaction.py b/paddle_billing/Resources/Transactions/Operations/UpdateTransaction.py index bd8dbc60..32fdc27a 100644 --- a/paddle_billing/Resources/Transactions/Operations/UpdateTransaction.py +++ b/paddle_billing/Resources/Transactions/Operations/UpdateTransaction.py @@ -10,13 +10,16 @@ TimePeriod, TransactionStatus, ) -from paddle_billing.Entities.Transactions import TransactionCreateItem, TransactionCreateItemWithPrice -from paddle_billing.Resources.Transactions.Operations.Update import UpdateBillingDetails +from paddle_billing.Resources.Transactions.Operations.Update import ( + TransactionUpdateItem, + TransactionUpdateItemWithPrice, + UpdateBillingDetails, +) @dataclass class UpdateTransaction(Operation): - items: list[TransactionCreateItem | TransactionCreateItemWithPrice] = Undefined() + items: list[TransactionUpdateItem | TransactionUpdateItemWithPrice] = Undefined() status: TransactionStatus | Undefined = Undefined() customer_id: str | None | Undefined = Undefined() address_id: str | None | Undefined = Undefined() diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/request/create_one_time_charge_full.json b/tests/Functional/Resources/Subscriptions/_fixtures/request/create_one_time_charge_full.json index da463e40..d5000819 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/request/create_one_time_charge_full.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/request/create_one_time_charge_full.json @@ -12,6 +12,72 @@ { "price_id": "pri_01h7zd9mzfq79850w4ryc39v38", "quantity": 845 + }, + { + "price": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "name": "some name", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "quantity": { + "maximum": 3, + "minimum": 1 + }, + "tax_mode": "account_setting", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + }, + "unit_price_overrides": [], + "billing_cycle": { + "frequency": 1, + "interval": "day" + }, + "trial_period": { + "frequency": 2, + "interval": "day" + } + }, + "quantity": 2 + }, + { + "price": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "name": "some name", + "product": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "image_url": "https://www.example.com/image.jpg", + "name": "some name", + "tax_category": "digital-goods" + }, + "quantity": { + "maximum": 3, + "minimum": 1 + }, + "tax_mode": "account_setting", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + }, + "unit_price_overrides": [], + "billing_cycle": { + "frequency": 1, + "interval": "day" + }, + "trial_period": { + "frequency": 2, + "interval": "day" + } + }, + "quantity": 2 } ], "on_payment_failure": "apply_change" diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_one_time_charge_full.json b/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_one_time_charge_full.json index 6844f1e5..4b36239b 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_one_time_charge_full.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_one_time_charge_full.json @@ -12,6 +12,102 @@ { "price_id": "pri_01h7zd9mzfq79850w4ryc39v38", "quantity": 845 + }, + { + "price": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "name": "some name", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "quantity": { + "maximum": 3, + "minimum": 1 + }, + "tax_mode": "account_setting", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + }, + "unit_price_overrides": [], + "billing_cycle": { + "frequency": 1, + "interval": "day" + }, + "trial_period": { + "frequency": 2, + "interval": "day" + } + }, + "quantity": 2 + }, + { + "price": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "name": "some name", + "product": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "image_url": "https://www.example.com/image.jpg", + "name": "some name", + "tax_category": "digital-goods" + }, + "quantity": { + "maximum": 3, + "minimum": 1 + }, + "tax_mode": "account_setting", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + }, + "unit_price_overrides": [], + "billing_cycle": { + "frequency": 1, + "interval": "day" + }, + "trial_period": { + "frequency": 2, + "interval": "day" + } + }, + "quantity": 2 + }, + { + "price": { + "description": "some description", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + } + }, + "quantity": 2 + }, + { + "price": { + "description": "some description", + "product": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "image_url": "https://www.example.com/image.jpg", + "name": "some name", + "tax_category": "digital-goods" + }, + "unit_price": { + "amount": "1", + "currency_code": "GBP" + } + }, + "quantity": 2 } ] } diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_update_full.json b/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_update_full.json index a1929b81..012cf875 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_update_full.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/request/preview_update_full.json @@ -13,7 +13,7 @@ "scheduled_change": null, "items": [ { "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", "quantity": 1 }, - { "price_id": "pri_01gsz91wy9k1yn7kx82bafwvea", "quantity": 5 }, + { "price_id": "pri_01gsz91wy9k1yn7kx82bafwvea" }, { "price": { "custom_data": { @@ -57,8 +57,7 @@ "description": "some description", "image_url": "https://www.example.com/image.jpg", "name": "some name", - "tax_category": "digital-goods", - "type": "custom" + "tax_category": "digital-goods" }, "quantity": { "maximum": 3, @@ -80,6 +79,36 @@ } }, "quantity": 2 + }, + { + "price": { + "description": "some description", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + } + }, + "quantity": 2 + }, + { + "price": { + "description": "some description", + "product": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "image_url": "https://www.example.com/image.jpg", + "name": "some name", + "tax_category": "digital-goods" + }, + "unit_price": { + "amount": "1", + "currency_code": "GBP" + } + }, + "quantity": 2 } ], "proration_billing_mode": "full_immediately", diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json b/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json index a1929b81..012cf875 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json @@ -13,7 +13,7 @@ "scheduled_change": null, "items": [ { "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", "quantity": 1 }, - { "price_id": "pri_01gsz91wy9k1yn7kx82bafwvea", "quantity": 5 }, + { "price_id": "pri_01gsz91wy9k1yn7kx82bafwvea" }, { "price": { "custom_data": { @@ -57,8 +57,7 @@ "description": "some description", "image_url": "https://www.example.com/image.jpg", "name": "some name", - "tax_category": "digital-goods", - "type": "custom" + "tax_category": "digital-goods" }, "quantity": { "maximum": 3, @@ -80,6 +79,36 @@ } }, "quantity": 2 + }, + { + "price": { + "description": "some description", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + } + }, + "quantity": 2 + }, + { + "price": { + "description": "some description", + "product": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "image_url": "https://www.example.com/image.jpg", + "name": "some name", + "tax_category": "digital-goods" + }, + "unit_price": { + "amount": "1", + "currency_code": "GBP" + } + }, + "quantity": 2 } ], "proration_billing_mode": "full_immediately", diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json index 392f0a47..3e4d6a29 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_charge_full_entity.json @@ -50,7 +50,6 @@ }, "line_items": [ { - "item_id": null, "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", "quantity": 10, "totals": { @@ -99,7 +98,6 @@ } }, { - "item_id": null, "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", "quantity": 1, "totals": { @@ -135,6 +133,43 @@ "ends_at": "2024-03-08T11:17:08.807055Z" } } + }, + { + "price_id": null, + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "product": { + "id": null, + "name": "Voice rooms addon", + "type": "standard", + "tax_category": "standard", + "description": "Create voice rooms in your chats to work in real time alongside your colleagues. Includes unlimited voice rooms and recording backup for compliance.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/GcZzBjXRfiraensppgtQ_icon2.png", + "custom_data": null, + "status": "active", + "import_meta": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "10000", + "discount": "0", + "tax": "887", + "total": "10887" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-02-08T11:17:08.807055Z", + "ends_at": "2024-03-08T11:17:08.807055Z" + } + } } ] }, @@ -171,7 +206,6 @@ }, "line_items": [ { - "item_id": null, "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", "quantity": 10, "totals": { @@ -220,7 +254,6 @@ } }, { - "item_id": null, "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", "quantity": 1, "totals": { @@ -256,6 +289,43 @@ "ends_at": "2024-04-08T11:17:08.807055Z" } } + }, + { + "price_id": null, + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "product": { + "id": null, + "name": "Voice rooms addon", + "type": "standard", + "tax_category": "standard", + "description": "Create voice rooms in your chats to work in real time alongside your colleagues. Includes unlimited voice rooms and recording backup for compliance.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/GcZzBjXRfiraensppgtQ_icon2.png", + "custom_data": null, + "status": "active", + "import_meta": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "10000", + "discount": "0", + "tax": "887", + "total": "10887" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-02-08T11:17:08.807055Z", + "ends_at": "2024-03-08T11:17:08.807055Z" + } + } } ] }, @@ -294,7 +364,6 @@ }, "line_items": [ { - "item_id": null, "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", "quantity": 1, "totals": { @@ -332,10 +401,109 @@ "ends_at": "2024-04-08T11:17:08.807055Z" } } + }, + { + "price_id": null, + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "product": { + "id": null, + "name": "Voice rooms addon", + "type": "standard", + "tax_category": "standard", + "description": "Create voice rooms in your chats to work in real time alongside your colleagues. Includes unlimited voice rooms and recording backup for compliance.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/GcZzBjXRfiraensppgtQ_icon2.png", + "custom_data": null, + "status": "active", + "import_meta": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "10000", + "discount": "0", + "tax": "887", + "total": "10887" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-02-08T11:17:08.807055Z", + "ends_at": "2024-03-08T11:17:08.807055Z" + } + } + }, + { + "price_id": null, + "quantity": 1, + "totals": { + "subtotal": "28498", + "tax": "2529", + "discount": "0", + "total": "31027" + }, + "product": { + "id": null, + "name": "VIP support", + "type": "standard", + "tax_category": "standard", + "description": "Get exclusive access to our expert team of product specialists, available to help you make the most of your ChatApp subscription.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/SW3OevDQ92dUHSkN5a2x_icon3.png", + "custom_data": null, + "status": "active", + "import_meta": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "28498", + "discount": "0", + "tax": "2529", + "total": "31027" + }, + "proration": { + "rate": "0.99993", + "billing_period": { + "starts_at": "2024-02-08T11:05:53.763Z", + "ends_at": "2024-03-08T11:02:03.946454Z" + } + } } ] }, - "adjustments": [] + "adjustments": [ + { + "transaction_id": "txn_01gsz8x8sawmvhz1pv30nge1ke", + "items": [ + { + "item_id": "txnitm_01gsz8x8sawmvhz1pv30nge1ke", + "type": "full", + "amount": null, + "proration": null, + "totals": { + "subtotal": "178488", + "tax": "15840", + "total": "194328" + } + } + ], + "totals": { + "subtotal": "39997", + "tax": "3549", + "total": "43546", + "fee": "2211", + "earnings": "37786", + "currency_code": "USD" + } + } + ] }, "scheduled_change": null, "items": [ diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json index c42c7251..f070ae76 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/preview_update_full_entity.json @@ -50,7 +50,6 @@ }, "line_items": [ { - "item_id": null, "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", "quantity": 50, "totals": { @@ -99,7 +98,6 @@ } }, { - "item_id": null, "price_id": "pri_01gsz95g2zrkagg294kpstx54r", "quantity": 1, "totals": { @@ -135,6 +133,43 @@ "ends_at": "2024-03-08T11:02:03.946454Z" } } + }, + { + "price_id": null, + "quantity": 1, + "totals": { + "subtotal": "28500", + "tax": "2529", + "discount": "0", + "total": "31029" + }, + "product": { + "id": null, + "name": "VIP support", + "type": "standard", + "tax_category": "standard", + "description": "Get exclusive access to our expert team of product specialists, available to help you make the most of your ChatApp subscription.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/SW3OevDQ92dUHSkN5a2x_icon3.png", + "custom_data": null, + "status": "active", + "import_meta": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "28500", + "discount": "0", + "tax": "2529", + "total": "31029" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-02-08T11:02:03.946454Z", + "ends_at": "2024-03-08T11:02:03.946454Z" + } + } } ] }, @@ -171,7 +206,6 @@ }, "line_items": [ { - "item_id": null, "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", "quantity": 50, "totals": { @@ -220,7 +254,6 @@ } }, { - "item_id": null, "price_id": "pri_01gsz95g2zrkagg294kpstx54r", "quantity": 1, "totals": { @@ -256,10 +289,72 @@ "ends_at": "2024-04-08T11:02:03.946454Z" } } + }, + { + "price_id": null, + "quantity": 1, + "totals": { + "subtotal": "28500", + "tax": "2529", + "discount": "0", + "total": "31029" + }, + "product": { + "id": null, + "name": "VIP support", + "type": "standard", + "tax_category": "standard", + "description": "Get exclusive access to our expert team of product specialists, available to help you make the most of your ChatApp subscription.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/SW3OevDQ92dUHSkN5a2x_icon3.png", + "custom_data": null, + "status": "active", + "import_meta": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "28500", + "discount": "0", + "tax": "2529", + "total": "31029" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-03-08T11:02:03.946454Z", + "ends_at": "2024-04-08T11:02:03.946454Z" + } + } } ] }, - "adjustments": [] + "adjustments": [ + { + "transaction_id": "txn_01gsz8x8sawmvhz1pv30nge1ke", + "items": [ + { + "item_id": "txnitm_01gsz8x8sawmvhz1pv30nge1ke", + "type": "full", + "amount": null, + "proration": null, + "totals": { + "subtotal": "178488", + "tax": "15840", + "total": "194328" + } + } + ], + "totals": { + "subtotal": "39997", + "tax": "3549", + "total": "43546", + "fee": "2211", + "earnings": "37786", + "currency_code": "USD" + } + } + ] }, "immediate_transaction": { "billing_period": { @@ -294,7 +389,6 @@ }, "line_items": [ { - "item_id": null, "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", "quantity": 50, "totals": { @@ -343,7 +437,6 @@ } }, { - "item_id": null, "price_id": "pri_01gsz95g2zrkagg294kpstx54r", "quantity": 1, "totals": { @@ -379,6 +472,43 @@ "ends_at": "2024-03-08T11:02:03.946454Z" } } + }, + { + "price_id": null, + "quantity": 1, + "totals": { + "subtotal": "28498", + "tax": "2529", + "discount": "0", + "total": "31027" + }, + "product": { + "id": null, + "name": "VIP support", + "type": "standard", + "tax_category": "standard", + "description": "Get exclusive access to our expert team of product specialists, available to help you make the most of your ChatApp subscription.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/SW3OevDQ92dUHSkN5a2x_icon3.png", + "custom_data": null, + "status": "active", + "import_meta": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "28498", + "discount": "0", + "tax": "2529", + "total": "31027" + }, + "proration": { + "rate": "0.99993", + "billing_period": { + "starts_at": "2024-02-08T11:05:53.763Z", + "ends_at": "2024-03-08T11:02:03.946454Z" + } + } } ] }, diff --git a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py index 892032a6..f248deb4 100644 --- a/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py +++ b/tests/Functional/Resources/Subscriptions/test_SubscriptionsClient.py @@ -19,18 +19,12 @@ PaymentMethodType, PriceQuantity, Interval, - CatalogType, TaxCategory, ImportMeta, ) from paddle_billing.Entities.Subscriptions import ( SubscriptionEffectiveFrom, - SubscriptionItems, - SubscriptionItemsWithPrice, - SubscriptionNonCatalogPrice, - SubscriptionNonCatalogPriceWithProduct, - SubscriptionNonCatalogProduct, SubscriptionOnPaymentFailure, SubscriptionProrationBillingMode, SubscriptionResumeEffectiveFrom, @@ -52,6 +46,23 @@ UpdateSubscription, ) + +from paddle_billing.Resources.Subscriptions.Operations.Update import ( + SubscriptionUpdateItem, + SubscriptionUpdateItemWithPrice, +) + +from paddle_billing.Resources.Subscriptions.Operations.Charge import ( + SubscriptionChargeItem, + SubscriptionChargeItemWithPrice, +) + +from paddle_billing.Resources.Subscriptions.Operations.Price import ( + SubscriptionNonCatalogPrice, + SubscriptionNonCatalogPriceWithProduct, + SubscriptionNonCatalogProduct, +) + from paddle_billing.Resources.Subscriptions.Operations.Update import UpdateBillingDetails from tests.Utils.ReadsFixture import ReadsFixtures @@ -98,42 +109,63 @@ class TestSubscriptionsClient: SubscriptionEffectiveFrom.NextBillingPeriod, ), items=[ - SubscriptionItems("pri_01gsz91wy9k1yn7kx82aafwvea", 1), - SubscriptionItems("pri_01gsz91wy9k1yn7kx82bafwvea", 5), - SubscriptionItemsWithPrice( + SubscriptionUpdateItem("pri_01gsz91wy9k1yn7kx82aafwvea", 1), + SubscriptionUpdateItem("pri_01gsz91wy9k1yn7kx82bafwvea"), + SubscriptionUpdateItemWithPrice( SubscriptionNonCatalogPrice( - "some description", - "some name", - "pro_01gsz4t5hdjse780zja8vvr7jg", - TaxMode.AccountSetting, - Money("1", CurrencyCode.GBP), - list(), - PriceQuantity(1, 3), - CustomData({"key": "value"}), - Duration(Interval.Day, 1), - Duration(Interval.Day, 2), + description="some description", + name="some name", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), ), 2, ), - SubscriptionItemsWithPrice( + SubscriptionUpdateItemWithPrice( SubscriptionNonCatalogPriceWithProduct( - "some description", - "some name", - SubscriptionNonCatalogProduct( - "some name", - "some description", - CatalogType.Custom, - TaxCategory.DigitalGoods, - "https://www.example.com/image.jpg", - CustomData({"key": "value"}), + description="some description", + name="some name", + product=SubscriptionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), ), - TaxMode.AccountSetting, - Money("1", CurrencyCode.GBP), - list(), - PriceQuantity(1, 3), - CustomData({"key": "value"}), - Duration(Interval.Day, 1), - Duration(Interval.Day, 2), + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), + SubscriptionUpdateItemWithPrice( + SubscriptionNonCatalogPrice( + description="some description", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + unit_price=Money("1", CurrencyCode.GBP), + ), + 2, + ), + SubscriptionUpdateItemWithPrice( + SubscriptionNonCatalogPriceWithProduct( + description="some description", + product=SubscriptionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), + ), + unit_price=Money("1", CurrencyCode.GBP), ), 2, ), @@ -779,7 +811,7 @@ def test_activate_subscription_returns_expected_response( "sub_01h8bx8fmywym11t6swgzba704", CreateOneTimeCharge( SubscriptionEffectiveFrom.NextBillingPeriod, - [SubscriptionItems("pri_01gsz98e27ak2tyhexptwc58yk", 1)], + [SubscriptionChargeItem("pri_01gsz98e27ak2tyhexptwc58yk", 1)], ), ReadsFixtures.read_raw_json_fixture("request/create_one_time_charge_minimal"), 200, @@ -791,9 +823,45 @@ def test_activate_subscription_returns_expected_response( CreateOneTimeCharge( SubscriptionEffectiveFrom.Immediately, [ - SubscriptionItems("pri_01gsz98e27ak2tyhexptwc58yk", 1), - SubscriptionItems("pri_01h7zdqstxe6djaefkqbkjy4k2", 10), - SubscriptionItems("pri_01h7zd9mzfq79850w4ryc39v38", 845), + SubscriptionChargeItem("pri_01gsz98e27ak2tyhexptwc58yk", 1), + SubscriptionChargeItem("pri_01h7zdqstxe6djaefkqbkjy4k2", 10), + SubscriptionChargeItem("pri_01h7zd9mzfq79850w4ryc39v38", 845), + SubscriptionChargeItemWithPrice( + SubscriptionNonCatalogPrice( + description="some description", + name="some name", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), + SubscriptionChargeItemWithPrice( + SubscriptionNonCatalogPriceWithProduct( + description="some description", + name="some name", + product=SubscriptionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), + ), + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), ], SubscriptionOnPaymentFailure.ApplyChange, ), @@ -883,42 +951,63 @@ def test_create_subscription_one_time_charge_uses_expected_payload( SubscriptionEffectiveFrom.NextBillingPeriod, ), items=[ - SubscriptionItems("pri_01gsz91wy9k1yn7kx82aafwvea", 1), - SubscriptionItems("pri_01gsz91wy9k1yn7kx82bafwvea", 5), - SubscriptionItemsWithPrice( + SubscriptionUpdateItem("pri_01gsz91wy9k1yn7kx82aafwvea", 1), + SubscriptionUpdateItem("pri_01gsz91wy9k1yn7kx82bafwvea"), + SubscriptionUpdateItemWithPrice( SubscriptionNonCatalogPrice( - "some description", - "some name", - "pro_01gsz4t5hdjse780zja8vvr7jg", - TaxMode.AccountSetting, - Money("1", CurrencyCode.GBP), - list(), - PriceQuantity(1, 3), - CustomData({"key": "value"}), - Duration(Interval.Day, 1), - Duration(Interval.Day, 2), + description="some description", + name="some name", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), ), 2, ), - SubscriptionItemsWithPrice( + SubscriptionUpdateItemWithPrice( SubscriptionNonCatalogPriceWithProduct( - "some description", - "some name", - SubscriptionNonCatalogProduct( - "some name", - "some description", - CatalogType.Custom, - TaxCategory.DigitalGoods, - "https://www.example.com/image.jpg", - CustomData({"key": "value"}), + description="some description", + name="some name", + product=SubscriptionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), ), - TaxMode.AccountSetting, - Money("1", CurrencyCode.GBP), - list(), - PriceQuantity(1, 3), - CustomData({"key": "value"}), - Duration(Interval.Day, 1), - Duration(Interval.Day, 2), + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), + SubscriptionUpdateItemWithPrice( + SubscriptionNonCatalogPrice( + description="some description", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + unit_price=Money("1", CurrencyCode.GBP), + ), + 2, + ), + SubscriptionUpdateItemWithPrice( + SubscriptionNonCatalogPriceWithProduct( + description="some description", + product=SubscriptionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), + ), + unit_price=Money("1", CurrencyCode.GBP), ), 2, ), @@ -1018,6 +1107,29 @@ def test_preview_update_subscription_has_import_meta( assert import_meta.external_id == "external-id" assert import_meta.imported_from == "external-platform" + def test_preview_create_subscription_handles_null_ids( + self, + test_client, + mock_requests, + ): + mock_requests.patch( + f"{test_client.base_url}/subscriptions/sub_01h8bx8fmywym11t6swgzba704/preview", + status_code=200, + text=ReadsFixtures.read_raw_json_fixture("response/preview_update_full_entity"), + ) + + response = test_client.client.subscriptions.preview_update( + "sub_01h8bx8fmywym11t6swgzba704", PreviewUpdateSubscription() + ) + + assert isinstance(response, SubscriptionPreview) + assert response.immediate_transaction.details.line_items[2].price_id is None + assert response.immediate_transaction.details.line_items[2].product.id is None + assert response.next_transaction.details.line_items[2].price_id is None + assert response.next_transaction.details.line_items[2].product.id is None + assert response.recurring_transaction_details.line_items[2].price_id is None + assert response.recurring_transaction_details.line_items[2].product.id is None + @mark.parametrize( "subscription_id, operation, expected_request_body, expected_response_status, expected_response_body, expected_url", [ @@ -1025,7 +1137,7 @@ def test_preview_update_subscription_has_import_meta( "sub_01h8bx8fmywym11t6swgzba704", PreviewOneTimeCharge( SubscriptionEffectiveFrom.NextBillingPeriod, - [SubscriptionItems("pri_01gsz98e27ak2tyhexptwc58yk", 1)], + [SubscriptionChargeItem("pri_01gsz98e27ak2tyhexptwc58yk", 1)], ), ReadsFixtures.read_raw_json_fixture("request/preview_one_time_charge_minimal"), 200, @@ -1037,9 +1149,67 @@ def test_preview_update_subscription_has_import_meta( PreviewOneTimeCharge( SubscriptionEffectiveFrom.Immediately, [ - SubscriptionItems("pri_01gsz98e27ak2tyhexptwc58yk", 1), - SubscriptionItems("pri_01h7zdqstxe6djaefkqbkjy4k2", 10), - SubscriptionItems("pri_01h7zd9mzfq79850w4ryc39v38", 845), + SubscriptionChargeItem("pri_01gsz98e27ak2tyhexptwc58yk", 1), + SubscriptionChargeItem("pri_01h7zdqstxe6djaefkqbkjy4k2", 10), + SubscriptionChargeItem("pri_01h7zd9mzfq79850w4ryc39v38", 845), + SubscriptionChargeItemWithPrice( + SubscriptionNonCatalogPrice( + description="some description", + name="some name", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), + SubscriptionChargeItemWithPrice( + SubscriptionNonCatalogPriceWithProduct( + description="some description", + name="some name", + product=SubscriptionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), + ), + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), + SubscriptionChargeItemWithPrice( + SubscriptionNonCatalogPrice( + description="some description", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + unit_price=Money("1", CurrencyCode.GBP), + ), + 2, + ), + SubscriptionChargeItemWithPrice( + SubscriptionNonCatalogPriceWithProduct( + description="some description", + product=SubscriptionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), + ), + unit_price=Money("1", CurrencyCode.GBP), + ), + 2, + ), ], ), ReadsFixtures.read_raw_json_fixture("request/preview_one_time_charge_full"), @@ -1100,7 +1270,8 @@ def test_preview_create_subscription_one_time_charge_has_import_meta( response = test_client.client.subscriptions.preview_one_time_charge( "sub_01h8bx8fmywym11t6swgzba704", PreviewOneTimeCharge( - SubscriptionEffectiveFrom.NextBillingPeriod, [SubscriptionItems("pri_01gsz98e27ak2tyhexptwc58yk", 1)] + SubscriptionEffectiveFrom.NextBillingPeriod, + [SubscriptionChargeItem("pri_01gsz98e27ak2tyhexptwc58yk", 1)], ), ) @@ -1110,3 +1281,30 @@ def test_preview_create_subscription_one_time_charge_has_import_meta( assert isinstance(import_meta, ImportMeta) assert import_meta.external_id == "external-id" assert import_meta.imported_from == "external-platform" + + def test_preview_create_subscription_one_time_charge_handles_null_ids( + self, + test_client, + mock_requests, + ): + mock_requests.post( + f"{test_client.base_url}/subscriptions/sub_01h8bx8fmywym11t6swgzba704/charge/preview", + status_code=200, + text=ReadsFixtures.read_raw_json_fixture("response/preview_charge_full_entity"), + ) + + response = test_client.client.subscriptions.preview_one_time_charge( + "sub_01h8bx8fmywym11t6swgzba704", + PreviewOneTimeCharge( + SubscriptionEffectiveFrom.NextBillingPeriod, + [SubscriptionChargeItem("pri_01gsz98e27ak2tyhexptwc58yk", 1)], + ), + ) + + assert isinstance(response, SubscriptionPreview) + assert response.immediate_transaction.details.line_items[2].price_id is None + assert response.immediate_transaction.details.line_items[2].product.id is None + assert response.next_transaction.details.line_items[2].price_id is None + assert response.next_transaction.details.line_items[2].product.id is None + assert response.recurring_transaction_details.line_items[2].price_id is None + assert response.recurring_transaction_details.line_items[2].product.id is None diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price.json b/tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price.json index 0b9ef815..911e7299 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price.json +++ b/tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price.json @@ -23,6 +23,68 @@ }, "custom_data": null } + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "name": "Annual (per seat)", + "product": { + "name": "Analytics addon", + "description": "Some Description", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": { + "key": "value" + } + }, + "billing_cycle": { + "interval": "year", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "30000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "quantity": { + "minimum": 10, + "maximum": 999 + }, + "custom_data": null + } + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Analytics addon", + "description": "Some Description", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": { + "key": "value" + } + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } } ] } diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_address_full.json b/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_address_full.json index c471bf05..04c3ac5b 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_address_full.json +++ b/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_address_full.json @@ -65,6 +65,36 @@ "custom_data": null }, "include_in_totals": true + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Analytics addon", + "description": "Some Description", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": { + "key": "value" + } + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } } ] } diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_customer_full.json b/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_customer_full.json index 3361419b..ca501b1d 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_customer_full.json +++ b/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_customer_full.json @@ -63,6 +63,36 @@ "custom_data": null }, "include_in_totals": true + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Analytics addon", + "description": "Some Description", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": { + "key": "value" + } + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } } ] } diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_ip_full.json b/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_ip_full.json index 10e8e2f8..e50d95e5 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_ip_full.json +++ b/tests/Functional/Resources/Transactions/_fixtures/request/preview_by_ip_full.json @@ -62,6 +62,36 @@ "custom_data": null }, "include_in_totals": true + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Analytics addon", + "description": "Some Description", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": { + "key": "value" + } + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } } ] } diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/update_full_items.json b/tests/Functional/Resources/Transactions/_fixtures/request/update_full_items.json new file mode 100644 index 00000000..46e09243 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/update_full_items.json @@ -0,0 +1,108 @@ +{ + "items": [ + { + "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", + "quantity": 1 + }, + { + "price_id": "pri_01gsz91wy9k1yn7kx82bafwvea", + "quantity": 5 + }, + { + "price": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "name": "some name", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "quantity": { + "maximum": 3, + "minimum": 1 + }, + "tax_mode": "account_setting", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + }, + "unit_price_overrides": [], + "billing_cycle": { + "frequency": 1, + "interval": "day" + }, + "trial_period": { + "frequency": 2, + "interval": "day" + } + }, + "quantity": 2 + }, + { + "price": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "name": "some name", + "product": { + "custom_data": { + "key": "value" + }, + "description": "some description", + "image_url": "https://www.example.com/image.jpg", + "name": "some name", + "tax_category": "digital-goods" + }, + "quantity": { + "maximum": 3, + "minimum": 1 + }, + "tax_mode": "account_setting", + "unit_price": { + "amount": "1", + "currency_code": "GBP" + }, + "unit_price_overrides": [], + "billing_cycle": { + "frequency": 1, + "interval": "day" + }, + "trial_period": { + "frequency": 2, + "interval": "day" + } + }, + "quantity": 2 + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } + }, + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Analytics addon", + "description": "Some Description", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": { + "key": "value" + } + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + } + } + ] +} \ No newline at end of file diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json b/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json index 5c7a35a8..8af29dca 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json @@ -75,6 +75,41 @@ "quantity": 1, "proration": null, "include_in_totals": false + }, + { + "price": { + "id": null, + "description": "One-time charge", + "name": "One-time charge", + "product_id": null, + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [ + { + "country_codes": ["AU"], + "unit_price": { + "amount": "40000", + "currency_code": "AUD" + } + } + ], + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "custom_data": null, + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "quantity": 1, + "proration": null, + "include_in_totals": false } ], "details": { @@ -104,36 +139,7 @@ }, "line_items": [ { - "price_id": "pri_01gsz8z1q1n00f12qt82y31smh", - "quantity": 20, - "totals": { - "subtotal": "600000", - "tax": "0", - "discount": "60000", - "total": "540000" - }, - "product": { - "id": "pro_01gsz4t5hdjse780zja8vvr7jg", - "name": "ChatApp Pro", - "description": "Everything in basic, plus access to a suite of powerful tools and features designed to take your team's productivity to the next level.", - "tax_category": "standard", - "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png", - "status": "active", - "custom_data": null, - "created_at": "2023-08-16T14:38:08.3Z", - "updated_at": "2023-08-16T14:38:08.3Z" - }, - "tax_rate": "0", - "unit_totals": { - "subtotal": "30000", - "tax": "0", - "discount": "3000", - "total": "27000" - }, - "proration": null - }, - { - "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "price_id": null, "quantity": 1, "totals": { "subtotal": "19900", @@ -142,9 +148,9 @@ "total": "17910" }, "product": { - "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "id": null, "name": "Custom domains", - "description": "Make ChatApp truly your own with custom domains! Custom domains reinforce your brand identity and make it easy for your team to access ChatApp.", + "description": null, "tax_category": "standard", "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/SW3OevDQ92dUHSkN5a2x_icon3.png", "status": "active", diff --git a/tests/Functional/Resources/Transactions/test_TransactionsClient.py b/tests/Functional/Resources/Transactions/test_TransactionsClient.py index ae4f9c02..9d66f475 100644 --- a/tests/Functional/Resources/Transactions/test_TransactionsClient.py +++ b/tests/Functional/Resources/Transactions/test_TransactionsClient.py @@ -27,16 +27,6 @@ Disposition, ) -from paddle_billing.Entities.Transactions import ( - TransactionCreateItem, - TransactionCreateItemWithPrice, - TransactionNonCatalogPrice, - TransactionNonCatalogPriceWithProduct, - TransactionNonCatalogProduct, - TransactionItemPreviewWithNonCatalogPrice, - TransactionItemPreviewWithPriceId, -) - from paddle_billing.Resources.Transactions.Operations import ( CreateTransaction, ListTransactions, @@ -49,8 +39,27 @@ GetTransactionInvoice, ) -from paddle_billing.Resources.Transactions.Operations.Create import CreateBillingDetails -from paddle_billing.Resources.Transactions.Operations.Update import UpdateBillingDetails +from paddle_billing.Resources.Transactions.Operations.Preview import ( + TransactionItemPreviewWithNonCatalogPrice, + TransactionItemPreviewWithPriceId, +) + +from paddle_billing.Resources.Transactions.Operations.Price import ( + TransactionNonCatalogPrice, + TransactionNonCatalogPriceWithProduct, + TransactionNonCatalogProduct, +) + +from paddle_billing.Resources.Transactions.Operations.Create import ( + CreateBillingDetails, + TransactionCreateItem, + TransactionCreateItemWithPrice, +) +from paddle_billing.Resources.Transactions.Operations.Update import ( + UpdateBillingDetails, + TransactionUpdateItem, + TransactionUpdateItemWithPrice, +) from tests.Utils.ReadsFixture import ReadsFixtures @@ -127,6 +136,7 @@ def test_list_transaction_can_paginate(self, test_client, mock_requests, operati CreateTransaction( items=[ TransactionCreateItemWithPrice( + quantity=20, price=TransactionNonCatalogPrice( description="Annual (per seat)", name="Annual (per seat)", @@ -135,11 +145,53 @@ def test_list_transaction_can_paginate(self, test_client, mock_requests, operati tax_mode=TaxMode.AccountSetting, unit_price=Money("30000", CurrencyCode.USD), unit_price_overrides=[], - quantity=PriceQuantity(10, 999), + quantity=PriceQuantity(minimum=10, maximum=999), custom_data=None, product_id="pro_01gsz4t5hdjse780zja8vvr7jg", ), + ), + TransactionCreateItemWithPrice( + quantity=20, + price=TransactionNonCatalogPriceWithProduct( + description="Annual (per seat)", + name="Annual (per seat)", + billing_cycle=Duration(Interval.Year, 1), + trial_period=None, + tax_mode=TaxMode.AccountSetting, + unit_price=Money("30000", CurrencyCode.USD), + unit_price_overrides=[], + quantity=PriceQuantity(minimum=10, maximum=999), + custom_data=None, + product=TransactionNonCatalogProduct( + name="Analytics addon", + description="Some Description", + image_url="https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + tax_category=TaxCategory.Standard, + custom_data=CustomData({"key": "value"}), + ), + ), + ), + TransactionCreateItemWithPrice( + quantity=20, + price=TransactionNonCatalogPrice( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + ), + ), + TransactionCreateItemWithPrice( quantity=20, + price=TransactionNonCatalogPriceWithProduct( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product=TransactionNonCatalogProduct( + name="Analytics addon", + description="Some Description", + image_url="https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + tax_category=TaxCategory.Standard, + custom_data=CustomData({"key": "value"}), + ), + ), ), ], ), @@ -324,12 +376,84 @@ def test_create_transaction_with_includes_returns_expected_payload( ReadsFixtures.read_raw_json_fixture("response/full_entity"), "/transactions/txn_01h7zcgmdc6tmwtjehp3sh7azf", ), + ( + "txn_01h7zcgmdc6tmwtjehp3sh7azf", + UpdateTransaction( + items=[ + TransactionUpdateItem("pri_01gsz91wy9k1yn7kx82aafwvea", 1), + TransactionUpdateItem("pri_01gsz91wy9k1yn7kx82bafwvea", 5), + TransactionUpdateItemWithPrice( + TransactionNonCatalogPrice( + description="some description", + name="some name", + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), + TransactionUpdateItemWithPrice( + TransactionNonCatalogPriceWithProduct( + description="some description", + name="some name", + product=TransactionNonCatalogProduct( + name="some name", + description="some description", + tax_category=TaxCategory.DigitalGoods, + image_url="https://www.example.com/image.jpg", + custom_data=CustomData({"key": "value"}), + ), + tax_mode=TaxMode.AccountSetting, + unit_price=Money("1", CurrencyCode.GBP), + unit_price_overrides=list(), + quantity=PriceQuantity(1, 3), + custom_data=CustomData({"key": "value"}), + billing_cycle=Duration(Interval.Day, 1), + trial_period=Duration(Interval.Day, 2), + ), + 2, + ), + TransactionUpdateItemWithPrice( + quantity=20, + price=TransactionNonCatalogPrice( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + ), + ), + TransactionUpdateItemWithPrice( + quantity=20, + price=TransactionNonCatalogPriceWithProduct( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product=TransactionNonCatalogProduct( + name="Analytics addon", + description="Some Description", + image_url="https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + tax_category=TaxCategory.Standard, + custom_data=CustomData({"key": "value"}), + ), + ), + ), + ], + ), + ReadsFixtures.read_raw_json_fixture("request/update_full_items"), + 200, + ReadsFixtures.read_raw_json_fixture("response/full_entity"), + "/transactions/txn_01h7zcgmdc6tmwtjehp3sh7azf", + ), ], ids=[ "Update transaction with single new value", "Update transaction with partial new values", "Create transaction with minimal billing details", "Create transaction with full billing details", + "Create transaction with items", ], ) def test_update_transaction_uses_expected_payload( @@ -757,6 +881,28 @@ def test_get_transaction_with_and_without_payment_method_id( ), ), ), + TransactionItemPreviewWithNonCatalogPrice( + quantity=20, + price=TransactionNonCatalogPrice( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + ), + ), + TransactionItemPreviewWithNonCatalogPrice( + quantity=20, + price=TransactionNonCatalogPriceWithProduct( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product=TransactionNonCatalogProduct( + name="Analytics addon", + description="Some Description", + image_url="https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + tax_category=TaxCategory.Standard, + custom_data=CustomData({"key": "value"}), + ), + ), + ), ], ), ReadsFixtures.read_raw_json_fixture("request/preview_by_address_full"), @@ -828,6 +974,28 @@ def test_get_transaction_with_and_without_payment_method_id( ), ), ), + TransactionItemPreviewWithNonCatalogPrice( + quantity=20, + price=TransactionNonCatalogPrice( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + ), + ), + TransactionItemPreviewWithNonCatalogPrice( + quantity=20, + price=TransactionNonCatalogPriceWithProduct( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product=TransactionNonCatalogProduct( + name="Analytics addon", + description="Some Description", + image_url="https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + tax_category=TaxCategory.Standard, + custom_data=CustomData({"key": "value"}), + ), + ), + ), ], ), ReadsFixtures.read_raw_json_fixture("request/preview_by_customer_full"), @@ -897,6 +1065,28 @@ def test_get_transaction_with_and_without_payment_method_id( ), ), ), + TransactionItemPreviewWithNonCatalogPrice( + quantity=20, + price=TransactionNonCatalogPrice( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product_id="pro_01gsz4t5hdjse780zja8vvr7jg", + ), + ), + TransactionItemPreviewWithNonCatalogPrice( + quantity=20, + price=TransactionNonCatalogPriceWithProduct( + description="Annual (per seat)", + unit_price=Money("30000", CurrencyCode.USD), + product=TransactionNonCatalogProduct( + name="Analytics addon", + description="Some Description", + image_url="https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + tax_category=TaxCategory.Standard, + custom_data=CustomData({"key": "value"}), + ), + ), + ), ], ), ReadsFixtures.read_raw_json_fixture("request/preview_by_ip_full"), @@ -946,6 +1136,36 @@ def test_preview_transaction_returns_expected_response( str(expected_response_body) ), "The response JSON doesn't match the expected fixture JSON" + def test_preview_transaction_handles_null_ids( + self, + test_client, + mock_requests, + ): + operation = PreviewTransactionByCustomer( + address_id="add_01hv8h6jj90jjz0d71m6hj4r9z", + customer_id="ctm_01h844q4mznqpgqgm6evgw1w63", + items=[ + TransactionItemPreviewWithPriceId( + price_id="pri_01he5kxqey1k8ankgef29cj4bv", + quantity=1, + include_in_totals=True, + ) + ], + ) + expected_response_body = ReadsFixtures.read_raw_json_fixture("response/preview_entity") + expected_path = "/transactions/preview" + + expected_url = f"{test_client.base_url}{expected_path}" + mock_requests.post(expected_url, status_code=200, text=expected_response_body) + + response = test_client.client.transactions.preview(operation) + + assert isinstance(response, TransactionPreview) + assert response.items[2].price.id is None + assert response.items[2].price.product_id is None + assert response.details.line_items[0].price_id is None + assert response.details.line_items[0].product.id is None + def test_get_transaction_invoice_pdf_returns_expected_response(self, test_client, mock_requests): transaction_id = "txn_01hen7bxc1p8ep4yk7n5jbzk9r" expected_response_status = 200 diff --git a/tests/Unit/Entities/Transactions/test_TransactionItemPreviewWithNonCatalogPrice.py b/tests/Unit/Entities/Transactions/test_TransactionItemPreviewWithNonCatalogPrice.py deleted file mode 100644 index 7ca80616..00000000 --- a/tests/Unit/Entities/Transactions/test_TransactionItemPreviewWithNonCatalogPrice.py +++ /dev/null @@ -1,133 +0,0 @@ -from paddle_billing.Entities.Transactions import ( - TransactionItemPreviewWithNonCatalogPrice, - TransactionNonCatalogProduct, - TransactionNonCatalogPrice, - TransactionNonCatalogPriceWithProduct, -) - -from paddle_billing.Entities.Shared import ( - CountryCode, - CurrencyCode, - Interval, - TaxCategory, -) - - -class TestTransactionItemPreviewWithNonCatalogPrice: - def test_from_dict_non_catalog_price(self): - item_preview = TransactionItemPreviewWithNonCatalogPrice.from_dict( - { - "quantity": 20, - "price": { - "name": "Some Name", - "description": "Some Description", - "product": { - "name": "Some Product Name", - "description": "Some Product Description", - "tax_category": "standard", - "image_url": "https://www.example.com/image.png", - "custom_data": {"key": "value"}, - }, - "billing_cycle": {"interval": "year", "frequency": 1}, - "trial_period": {"interval": "month", "frequency": 3}, - "tax_mode": "account_setting", - "unit_price": {"amount": "30000", "currency_code": "USD"}, - "unit_price_overrides": [ - { - "unit_price": { - "amount": "20", - "currency_code": "USD", - }, - "country_codes": ["US"], - }, - ], - "quantity": {"minimum": 10, "maximum": 999}, - "custom_data": {"key": "value"}, - }, - "include_in_totals": True, - } - ) - - assert isinstance(item_preview, TransactionItemPreviewWithNonCatalogPrice) - - assert item_preview.quantity == 20 - assert item_preview.include_in_totals is True - - price = item_preview.price - - assert isinstance(price, TransactionNonCatalogPriceWithProduct) - assert price.name == "Some Name" - assert price.description == "Some Description" - assert price.billing_cycle.interval == Interval.Year - assert price.billing_cycle.frequency == 1 - assert price.trial_period.interval == Interval.Month - assert price.trial_period.frequency == 3 - assert price.unit_price.amount == "30000" - assert price.unit_price.currency_code == CurrencyCode.USD - assert price.unit_price_overrides[0].unit_price.amount == "20" - assert price.unit_price_overrides[0].unit_price.currency_code == CurrencyCode.USD - assert price.unit_price_overrides[0].country_codes == [CountryCode.US] - assert price.quantity.minimum == 10 - assert price.quantity.maximum == 999 - assert price.custom_data.data["key"] == "value" - - product = price.product - - assert isinstance(product, TransactionNonCatalogProduct) - assert product.name == "Some Product Name" - assert product.description == "Some Product Description" - assert product.image_url == "https://www.example.com/image.png" - assert product.tax_category == TaxCategory.Standard - assert product.custom_data.data["key"] == "value" - - def test_from_dict_catalog_price(self): - item_preview = TransactionItemPreviewWithNonCatalogPrice.from_dict( - { - "quantity": 20, - "price": { - "name": "Some Name", - "description": "Some Description", - "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", - "billing_cycle": {"interval": "year", "frequency": 1}, - "trial_period": {"interval": "month", "frequency": 3}, - "tax_mode": "account_setting", - "unit_price": {"amount": "30000", "currency_code": "USD"}, - "unit_price_overrides": [ - { - "unit_price": { - "amount": "20", - "currency_code": "USD", - }, - "country_codes": ["US"], - }, - ], - "quantity": {"minimum": 10, "maximum": 999}, - "custom_data": {"key": "value"}, - }, - "include_in_totals": False, - } - ) - - assert isinstance(item_preview, TransactionItemPreviewWithNonCatalogPrice) - - assert item_preview.quantity == 20 - assert item_preview.include_in_totals is False - - price = item_preview.price - - assert isinstance(price, TransactionNonCatalogPrice) - assert price.name == "Some Name" - assert price.description == "Some Description" - assert price.billing_cycle.interval == Interval.Year - assert price.billing_cycle.frequency == 1 - assert price.trial_period.interval == Interval.Month - assert price.trial_period.frequency == 3 - assert price.unit_price.amount == "30000" - assert price.unit_price.currency_code == CurrencyCode.USD - assert price.unit_price_overrides[0].unit_price.amount == "20" - assert price.unit_price_overrides[0].unit_price.currency_code == CurrencyCode.USD - assert price.unit_price_overrides[0].country_codes == [CountryCode.US] - assert price.quantity.minimum == 10 - assert price.quantity.maximum == 999 - assert price.custom_data.data["key"] == "value" - assert price.product_id == "pro_01gsz4t5hdjse780zja8vvr7jg"