From 807fd31630692ec4eeff74180b2db8c3c83650c0 Mon Sep 17 00:00:00 2001 From: davidgrayston-paddle Date: Wed, 9 Oct 2024 12:20:25 +0100 Subject: [PATCH] fix: Transaction Preview Price (#89) * fix: Separate transaction preview operations and nullable preview price IDs * fix: Add TransactionPreviewPrice with nullable ID * fix Add support for transaction update items with optional properties --- CHANGELOG.md | 24 ++ .../Shared/TransactionLineItemPreview.php | 2 +- .../Transaction/TransactionCreateItem.php | 3 + .../TransactionCreateItemWithPrice.php | 3 + ...nsactionItemPreviewWithNonCatalogPrice.php | 3 + .../TransactionItemPreviewWithPrice.php | 4 +- .../TransactionItemPreviewWithPriceId.php | 3 + .../TransactionNonCatalogPrice.php | 3 + .../TransactionNonCatalogPriceWithProduct.php | 3 + .../TransactionNonCatalogProduct.php | 3 + .../Transaction/TransactionPreviewPrice.php | 78 ++++ .../TransactionUpdateTransactionItem.php | 3 + .../Create/TransactionCreateItem.php | 14 + .../Create/TransactionCreateItemWithPrice.php | 17 + .../Operations/CreateTransaction.php | 8 +- ...nsactionItemPreviewWithNonCatalogPrice.php | 31 ++ .../TransactionItemPreviewWithPriceId.php | 29 ++ .../Operations/PreviewTransaction.php | 8 +- .../Price/TransactionNonCatalogPrice.php | 52 +++ .../TransactionNonCatalogPriceWithProduct.php | 52 +++ .../Price/TransactionNonCatalogProduct.php | 35 ++ .../Update/TransactionUpdateItem.php | 14 + .../Update/TransactionUpdateItemWithPrice.php | 17 + .../Operations/UpdateTransaction.php | 4 +- .../Transactions/TransactionsClientTest.php | 403 +++++++++++++++++- ...ultiple_non_catalog_price_and_product.json | 49 +++ ...te_with_non_catalog_price_and_product.json | 34 ++ .../preview_with_basic_non_catalog_price.json | 16 + ...ultiple_non_catalog_price_and_product.json | 50 +++ ...ew_with_non_catalog_price_and_product.json | 35 ++ .../request/update_with_catalog_price.json | 9 + .../update_with_non_catalog_price.json | 28 ++ ...te_with_non_catalog_price_and_product.json | 49 +++ .../_fixtures/response/preview_entity.json | 6 +- 34 files changed, 1074 insertions(+), 18 deletions(-) create mode 100644 src/Entities/Transaction/TransactionPreviewPrice.php create mode 100644 src/Resources/Transactions/Operations/Create/TransactionCreateItem.php create mode 100644 src/Resources/Transactions/Operations/Create/TransactionCreateItemWithPrice.php create mode 100644 src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithNonCatalogPrice.php create mode 100644 src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithPriceId.php create mode 100644 src/Resources/Transactions/Operations/Price/TransactionNonCatalogPrice.php create mode 100644 src/Resources/Transactions/Operations/Price/TransactionNonCatalogPriceWithProduct.php create mode 100644 src/Resources/Transactions/Operations/Price/TransactionNonCatalogProduct.php create mode 100644 src/Resources/Transactions/Operations/Update/TransactionUpdateItem.php create mode 100644 src/Resources/Transactions/Operations/Update/TransactionUpdateItemWithPrice.php create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/create_with_multiple_non_catalog_price_and_product.json create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price_and_product.json create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/preview_with_basic_non_catalog_price.json create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/preview_with_multiple_non_catalog_price_and_product.json create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/preview_with_non_catalog_price_and_product.json create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/update_with_catalog_price.json create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price.json create mode 100644 tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price_and_product.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 993151f..e027ca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,30 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx ### Fixed - Dropped `receipt_data` on create and preview of a one-time charge for Subscriptions and Transactions +- `TransactionsClient::preview()` `TransactionPreview` response now allows null price ID for non-catalog prices: + - `TransactionPreview` `items[]->price` can now return `Price` (with `id`) or `TransactionPreviewPrice` (with nullable `id`) + - `TransactionPreview` `details->lineItems[]->priceId` is now nullable + +### Added +- `TransactionsClient::create()` now supports operation items with optional properties: + - `Resources\Transactions\Operations\Create\TransactionCreateItem` + - `Resources\Transactions\Operations\Create\TransactionCreateItemWithPrice` +- `TransactionsClient::update()` now supports operation items with optional properties: + - `Resources\Transactions\Operations\Update\TransactionUpdateItem` + - `Resources\Transactions\Operations\Update\TransactionUpdateItemWithPrice` +- `TransactionsClient::preview()` now supports operation items with optional properties: + - `Resources\Transactions\Operations\Preview\TransactionItemPreviewWithNonCatalogPrice` + - `Resources\Transactions\Operations\Preview\TransactionItemPreviewWithPriceId` + +### Deprecated +- `TransactionsClient::create()` operation items have been deprecated: + - `Entities\Transaction\TransactionCreateItem` + - `Entities\Transaction\TransactionCreateItemWithPrice` +- `TransactionsClient::update()` operation items have been deprecated: + - `Entities\Transaction\TransactionUpdateTransactionItem` +- `TransactionsClient::preview()` operation items have been deprecated: + - `Entities\Transaction\TransactionItemPreviewWithNonCatalogPrice` + - `Entities\Transaction\TransactionItemPreviewWithPriceId` # [1.3.1] - 2024-09-30 diff --git a/src/Entities/Shared/TransactionLineItemPreview.php b/src/Entities/Shared/TransactionLineItemPreview.php index 19ad8c6..80a902d 100644 --- a/src/Entities/Shared/TransactionLineItemPreview.php +++ b/src/Entities/Shared/TransactionLineItemPreview.php @@ -16,7 +16,7 @@ class TransactionLineItemPreview { private function __construct( - public string $priceId, + public string|null $priceId, public int $quantity, public string $taxRate, public UnitTotals $unitTotals, diff --git a/src/Entities/Transaction/TransactionCreateItem.php b/src/Entities/Transaction/TransactionCreateItem.php index d971a4c..e177209 100644 --- a/src/Entities/Transaction/TransactionCreateItem.php +++ b/src/Entities/Transaction/TransactionCreateItem.php @@ -11,6 +11,9 @@ namespace Paddle\SDK\Entities\Transaction; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Create\TransactionCreateItem + */ class TransactionCreateItem { public function __construct( diff --git a/src/Entities/Transaction/TransactionCreateItemWithPrice.php b/src/Entities/Transaction/TransactionCreateItemWithPrice.php index fc565fd..ab8af9c 100644 --- a/src/Entities/Transaction/TransactionCreateItemWithPrice.php +++ b/src/Entities/Transaction/TransactionCreateItemWithPrice.php @@ -11,6 +11,9 @@ namespace Paddle\SDK\Entities\Transaction; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Create\TransactionCreateItemWithPrice + */ class TransactionCreateItemWithPrice { public function __construct( diff --git a/src/Entities/Transaction/TransactionItemPreviewWithNonCatalogPrice.php b/src/Entities/Transaction/TransactionItemPreviewWithNonCatalogPrice.php index d1c44c5..483223b 100644 --- a/src/Entities/Transaction/TransactionItemPreviewWithNonCatalogPrice.php +++ b/src/Entities/Transaction/TransactionItemPreviewWithNonCatalogPrice.php @@ -11,6 +11,9 @@ namespace Paddle\SDK\Entities\Transaction; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Preview\TransactionItemPreviewWithNonCatalogPrice + */ class TransactionItemPreviewWithNonCatalogPrice { public function __construct( diff --git a/src/Entities/Transaction/TransactionItemPreviewWithPrice.php b/src/Entities/Transaction/TransactionItemPreviewWithPrice.php index 3b8c12d..a6ef78c 100644 --- a/src/Entities/Transaction/TransactionItemPreviewWithPrice.php +++ b/src/Entities/Transaction/TransactionItemPreviewWithPrice.php @@ -16,7 +16,7 @@ class TransactionItemPreviewWithPrice { private function __construct( - public Price $price, + public Price|TransactionPreviewPrice $price, public int $quantity, public bool $includeInTotals, public TransactionProration|null $proration, @@ -26,7 +26,7 @@ private function __construct( public static function from(array $data): self { return new self( - Price::from($data['price']), + isset($data['price']['id']) ? Price::from($data['price']) : TransactionPreviewPrice::from($data['price']), $data['quantity'], $data['include_in_totals'], isset($data['proration']) ? TransactionProration::from($data['proration']) : null, diff --git a/src/Entities/Transaction/TransactionItemPreviewWithPriceId.php b/src/Entities/Transaction/TransactionItemPreviewWithPriceId.php index 88ed14d..a3ec9b3 100644 --- a/src/Entities/Transaction/TransactionItemPreviewWithPriceId.php +++ b/src/Entities/Transaction/TransactionItemPreviewWithPriceId.php @@ -11,6 +11,9 @@ namespace Paddle\SDK\Entities\Transaction; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Preview\TransactionItemPreviewWithPriceId + */ class TransactionItemPreviewWithPriceId { public function __construct( diff --git a/src/Entities/Transaction/TransactionNonCatalogPrice.php b/src/Entities/Transaction/TransactionNonCatalogPrice.php index 99614e3..03a689c 100644 --- a/src/Entities/Transaction/TransactionNonCatalogPrice.php +++ b/src/Entities/Transaction/TransactionNonCatalogPrice.php @@ -18,6 +18,9 @@ use Paddle\SDK\Entities\Shared\TimePeriod; use Paddle\SDK\Entities\Shared\UnitPriceOverride; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Price\TransactionNonCatalogPrice + */ class TransactionNonCatalogPrice { /** diff --git a/src/Entities/Transaction/TransactionNonCatalogPriceWithProduct.php b/src/Entities/Transaction/TransactionNonCatalogPriceWithProduct.php index d020966..a617e81 100644 --- a/src/Entities/Transaction/TransactionNonCatalogPriceWithProduct.php +++ b/src/Entities/Transaction/TransactionNonCatalogPriceWithProduct.php @@ -18,6 +18,9 @@ use Paddle\SDK\Entities\Shared\TimePeriod; use Paddle\SDK\Entities\Shared\UnitPriceOverride; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Price\TransactionNonCatalogPriceWithProduct + */ class TransactionNonCatalogPriceWithProduct { /** diff --git a/src/Entities/Transaction/TransactionNonCatalogProduct.php b/src/Entities/Transaction/TransactionNonCatalogProduct.php index 533eb30..069ab53 100644 --- a/src/Entities/Transaction/TransactionNonCatalogProduct.php +++ b/src/Entities/Transaction/TransactionNonCatalogProduct.php @@ -14,6 +14,9 @@ use Paddle\SDK\Entities\Shared\CustomData; use Paddle\SDK\Entities\Shared\TaxCategory; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Price\TransactionNonCatalogProduct + */ class TransactionNonCatalogProduct { public function __construct( diff --git a/src/Entities/Transaction/TransactionPreviewPrice.php b/src/Entities/Transaction/TransactionPreviewPrice.php new file mode 100644 index 0000000..38eee34 --- /dev/null +++ b/src/Entities/Transaction/TransactionPreviewPrice.php @@ -0,0 +1,78 @@ + $unitPriceOverrides + */ + private function __construct( + public string|null $id, + public string $productId, + public string|null $name, + public string $description, + public CatalogType|null $type, + public TimePeriod|null $billingCycle, + public TimePeriod|null $trialPeriod, + public TaxMode $taxMode, + public Money $unitPrice, + public array $unitPriceOverrides, + public PriceQuantity $quantity, + public Status $status, + public CustomData|null $customData, + public ImportMeta|null $importMeta, + public Product|null $product, + public \DateTimeInterface $createdAt, + public \DateTimeInterface $updatedAt, + ) { + } + + public static function from(array $data): self + { + return new self( + id: $data['id'], + productId: $data['product_id'], + name: $data['name'] ?? null, + description: $data['description'], + type: CatalogType::from($data['type'] ?? ''), + billingCycle: isset($data['billing_cycle']) ? TimePeriod::from($data['billing_cycle']) : null, + trialPeriod: isset($data['trial_period']) ? TimePeriod::from($data['trial_period']) : null, + taxMode: TaxMode::from($data['tax_mode']), + unitPrice: Money::from($data['unit_price']), + unitPriceOverrides: array_map( + fn (array $override): UnitPriceOverride => UnitPriceOverride::from($override), + $data['unit_price_overrides'] ?? [], + ), + quantity: PriceQuantity::from($data['quantity']), + status: Status::from($data['status']), + customData: isset($data['custom_data']) ? new CustomData($data['custom_data']) : null, + importMeta: isset($data['import_meta']) ? ImportMeta::from($data['import_meta']) : null, + product: isset($data['product']) ? Product::from($data['product']) : null, + createdAt: DateTime::from($data['created_at']), + updatedAt: DateTime::from($data['updated_at']), + ); + } +} diff --git a/src/Entities/Transaction/TransactionUpdateTransactionItem.php b/src/Entities/Transaction/TransactionUpdateTransactionItem.php index 695bb1b..01284be 100644 --- a/src/Entities/Transaction/TransactionUpdateTransactionItem.php +++ b/src/Entities/Transaction/TransactionUpdateTransactionItem.php @@ -11,6 +11,9 @@ namespace Paddle\SDK\Entities\Transaction; +/** + * @deprecated Replaced by \Paddle\SDK\Resources\Transactions\Operations\Update\TransactionUpdateItem + */ class TransactionUpdateTransactionItem { public function __construct( diff --git a/src/Resources/Transactions/Operations/Create/TransactionCreateItem.php b/src/Resources/Transactions/Operations/Create/TransactionCreateItem.php new file mode 100644 index 0000000..39b3b9e --- /dev/null +++ b/src/Resources/Transactions/Operations/Create/TransactionCreateItem.php @@ -0,0 +1,14 @@ + $items + * @param array $items */ public function __construct( public readonly array $items, diff --git a/src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithNonCatalogPrice.php b/src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithNonCatalogPrice.php new file mode 100644 index 0000000..7873cf1 --- /dev/null +++ b/src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithNonCatalogPrice.php @@ -0,0 +1,31 @@ +filterUndefined([ + 'price' => $this->price, + 'quantity' => $this->quantity, + 'include_in_totals' => $this->includeInTotals, + ]); + } +} diff --git a/src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithPriceId.php b/src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithPriceId.php new file mode 100644 index 0000000..cdec853 --- /dev/null +++ b/src/Resources/Transactions/Operations/Preview/TransactionItemPreviewWithPriceId.php @@ -0,0 +1,29 @@ +filterUndefined([ + 'price_id' => $this->priceId, + 'quantity' => $this->quantity, + 'include_in_totals' => $this->includeInTotals, + ]); + } +} diff --git a/src/Resources/Transactions/Operations/PreviewTransaction.php b/src/Resources/Transactions/Operations/PreviewTransaction.php index ab472a8..690c783 100644 --- a/src/Resources/Transactions/Operations/PreviewTransaction.php +++ b/src/Resources/Transactions/Operations/PreviewTransaction.php @@ -7,9 +7,11 @@ use Paddle\SDK\Entities\Shared\AddressPreview; use Paddle\SDK\Entities\Shared\CollectionMode; use Paddle\SDK\Entities\Shared\CurrencyCode; -use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithNonCatalogPrice; -use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithPriceId; +use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithNonCatalogPrice as EntityItemPreviewWithNonCatalogPrice; +use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithPriceId as EntityItemPreviewWithPriceId; use Paddle\SDK\FiltersUndefined; +use Paddle\SDK\Resources\Transactions\Operations\Preview\TransactionItemPreviewWithNonCatalogPrice; +use Paddle\SDK\Resources\Transactions\Operations\Preview\TransactionItemPreviewWithPriceId; use Paddle\SDK\Undefined; class PreviewTransaction implements \JsonSerializable @@ -17,7 +19,7 @@ class PreviewTransaction implements \JsonSerializable use FiltersUndefined; /** - * @param array $items + * @param array $items */ public function __construct( public readonly array $items, diff --git a/src/Resources/Transactions/Operations/Price/TransactionNonCatalogPrice.php b/src/Resources/Transactions/Operations/Price/TransactionNonCatalogPrice.php new file mode 100644 index 0000000..6cf03b8 --- /dev/null +++ b/src/Resources/Transactions/Operations/Price/TransactionNonCatalogPrice.php @@ -0,0 +1,52 @@ + $unitPriceOverrides + */ + public function __construct( + public string $description, + public Money $unitPrice, + public string $productId, + public string|Undefined|null $name = new Undefined(), + public TimePeriod|Undefined|null $billingCycle = new Undefined(), + public TimePeriod|Undefined|null $trialPeriod = new Undefined(), + public TaxMode|Undefined $taxMode = new Undefined(), + public array|Undefined $unitPriceOverrides = new Undefined(), + public PriceQuantity|Undefined $quantity = new Undefined(), + public CustomData|Undefined|null $customData = new Undefined(), + ) { + } + + public function jsonSerialize(): array + { + return $this->filterUndefined([ + 'description' => $this->description, + 'unit_price' => $this->unitPrice, + 'product_id' => $this->productId, + 'name' => $this->name, + 'billing_cycle' => $this->billingCycle, + 'trial_period' => $this->trialPeriod, + 'tax_mode' => $this->taxMode, + 'unit_price_overrides' => $this->unitPriceOverrides, + 'quantity' => $this->quantity, + 'custom_data' => $this->customData, + ]); + } +} diff --git a/src/Resources/Transactions/Operations/Price/TransactionNonCatalogPriceWithProduct.php b/src/Resources/Transactions/Operations/Price/TransactionNonCatalogPriceWithProduct.php new file mode 100644 index 0000000..4cb2ca3 --- /dev/null +++ b/src/Resources/Transactions/Operations/Price/TransactionNonCatalogPriceWithProduct.php @@ -0,0 +1,52 @@ + $unitPriceOverrides + */ + public function __construct( + public string $description, + public Money $unitPrice, + public TransactionNonCatalogProduct $product, + public string|Undefined|null $name = new Undefined(), + public TimePeriod|Undefined|null $billingCycle = new Undefined(), + public TimePeriod|Undefined|null $trialPeriod = new Undefined(), + public TaxMode|Undefined $taxMode = new Undefined(), + public array|Undefined $unitPriceOverrides = new Undefined(), + public PriceQuantity|Undefined $quantity = new Undefined(), + public CustomData|Undefined|null $customData = new Undefined(), + ) { + } + + public function jsonSerialize(): array + { + return $this->filterUndefined([ + 'description' => $this->description, + 'unit_price' => $this->unitPrice, + 'product' => $this->product, + 'name' => $this->name, + 'billing_cycle' => $this->billingCycle, + 'trial_period' => $this->trialPeriod, + 'tax_mode' => $this->taxMode, + 'unit_price_overrides' => $this->unitPriceOverrides, + 'quantity' => $this->quantity, + 'custom_data' => $this->customData, + ]); + } +} diff --git a/src/Resources/Transactions/Operations/Price/TransactionNonCatalogProduct.php b/src/Resources/Transactions/Operations/Price/TransactionNonCatalogProduct.php new file mode 100644 index 0000000..fd11082 --- /dev/null +++ b/src/Resources/Transactions/Operations/Price/TransactionNonCatalogProduct.php @@ -0,0 +1,35 @@ +filterUndefined([ + 'name' => $this->name, + 'description' => $this->description, + 'tax_category' => $this->taxCategory, + 'image_url' => $this->imageUrl, + 'custom_data' => $this->customData, + ]); + } +} diff --git a/src/Resources/Transactions/Operations/Update/TransactionUpdateItem.php b/src/Resources/Transactions/Operations/Update/TransactionUpdateItem.php new file mode 100644 index 0000000..b9e6b01 --- /dev/null +++ b/src/Resources/Transactions/Operations/Update/TransactionUpdateItem.php @@ -0,0 +1,14 @@ + $items + * @param array $items */ public function __construct( public readonly TransactionStatus|Undefined $status = new Undefined(), diff --git a/tests/Functional/Resources/Transactions/TransactionsClientTest.php b/tests/Functional/Resources/Transactions/TransactionsClientTest.php index fbdaf17..8e16992 100644 --- a/tests/Functional/Resources/Transactions/TransactionsClientTest.php +++ b/tests/Functional/Resources/Transactions/TransactionsClientTest.php @@ -7,6 +7,7 @@ use GuzzleHttp\Psr7\Response; use Http\Mock\Client as MockClient; use Paddle\SDK\Client; +use Paddle\SDK\Entities\Price; use Paddle\SDK\Entities\Shared\BillingDetails; use Paddle\SDK\Entities\Shared\CollectionMode; use Paddle\SDK\Entities\Shared\CurrencyCode; @@ -15,26 +16,40 @@ use Paddle\SDK\Entities\Shared\Interval; use Paddle\SDK\Entities\Shared\Money; use Paddle\SDK\Entities\Shared\PriceQuantity; +use Paddle\SDK\Entities\Shared\TaxCategory; use Paddle\SDK\Entities\Shared\TaxMode; use Paddle\SDK\Entities\Shared\TimePeriod; use Paddle\SDK\Entities\Shared\TransactionStatus; use Paddle\SDK\Entities\Transaction; -use Paddle\SDK\Entities\Transaction\TransactionCreateItem; -use Paddle\SDK\Entities\Transaction\TransactionCreateItemWithPrice; -use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithNonCatalogPrice; -use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithPriceId; -use Paddle\SDK\Entities\Transaction\TransactionNonCatalogPrice; +use Paddle\SDK\Entities\Transaction\TransactionCreateItem as DeprecatedTransactionCreateItem; +use Paddle\SDK\Entities\Transaction\TransactionCreateItemWithPrice as DeprecatedTransactionCreateItemWithPrice; +use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithNonCatalogPrice as DeprecatedTransactionItemPreviewWithNonCatalogPrice; +use Paddle\SDK\Entities\Transaction\TransactionItemPreviewWithPriceId as DeprecatedTransactionItemPreviewWithPriceId; +use Paddle\SDK\Entities\Transaction\TransactionNonCatalogPrice as DeprecatedTransactionNonCatalogPrice; +use Paddle\SDK\Entities\Transaction\TransactionNonCatalogPriceWithProduct as DeprecatedTransactionNonCatalogPriceWithProduct; +use Paddle\SDK\Entities\Transaction\TransactionNonCatalogProduct as DeprecatedTransactionNonCatalogProduct; +use Paddle\SDK\Entities\Transaction\TransactionPreviewPrice; +use Paddle\SDK\Entities\Transaction\TransactionUpdateTransactionItem as DeprecatedTransactionUpdateTransactionItem; use Paddle\SDK\Environment; use Paddle\SDK\Options; use Paddle\SDK\Resources\Shared\Operations\List\Comparator; use Paddle\SDK\Resources\Shared\Operations\List\DateComparison; use Paddle\SDK\Resources\Shared\Operations\List\Pager; +use Paddle\SDK\Resources\Transactions\Operations\Create\TransactionCreateItem; +use Paddle\SDK\Resources\Transactions\Operations\Create\TransactionCreateItemWithPrice; use Paddle\SDK\Resources\Transactions\Operations\CreateTransaction; use Paddle\SDK\Resources\Transactions\Operations\GetTransactionInvoice; use Paddle\SDK\Resources\Transactions\Operations\List\Includes; use Paddle\SDK\Resources\Transactions\Operations\List\Origin; use Paddle\SDK\Resources\Transactions\Operations\ListTransactions; +use Paddle\SDK\Resources\Transactions\Operations\Preview\TransactionItemPreviewWithNonCatalogPrice; +use Paddle\SDK\Resources\Transactions\Operations\Preview\TransactionItemPreviewWithPriceId; use Paddle\SDK\Resources\Transactions\Operations\PreviewTransaction; +use Paddle\SDK\Resources\Transactions\Operations\Price\TransactionNonCatalogPrice; +use Paddle\SDK\Resources\Transactions\Operations\Price\TransactionNonCatalogPriceWithProduct; +use Paddle\SDK\Resources\Transactions\Operations\Price\TransactionNonCatalogProduct; +use Paddle\SDK\Resources\Transactions\Operations\Update\TransactionUpdateItem; +use Paddle\SDK\Resources\Transactions\Operations\Update\TransactionUpdateItemWithPrice; use Paddle\SDK\Resources\Transactions\Operations\UpdateTransaction; use Paddle\SDK\Tests\Utils\ReadsFixtures; use PHPUnit\Framework\TestCase; @@ -137,11 +152,87 @@ public static function createOperationsProvider(): \Generator self::readRawJsonFixture('request/create_basic'), ]; + yield 'Basic Create (deprecated)' => [ + new CreateTransaction( + items: [ + new DeprecatedTransactionCreateItem('pri_01he5kxqey1k8ankgef29cj4bv', 1), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/create_basic'), + ]; + yield 'Create with non catalog price' => [ new CreateTransaction( items: [ new TransactionCreateItemWithPrice( new TransactionNonCatalogPrice( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + 'pro_01gsz4t5hdjse780zja8vvr7jg', + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + [], + new PriceQuantity(10, 999), + null, + ), + 20, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/create_with_non_catalog_price'), + ]; + + yield 'Create with non catalog price and product' => [ + new CreateTransaction( + items: [ + new TransactionCreateItemWithPrice( + new TransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + new TransactionNonCatalogProduct( + 'Annual (per seat)', + TaxCategory::DigitalGoods(), + 'Some description', + 'https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png', + new CustomData([]), + ), + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + [], + new PriceQuantity(10, 999), + null, + ), + 20, + ), + new TransactionItemPreviewWithNonCatalogPrice( + new TransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + new TransactionNonCatalogProduct( + 'Annual (per seat)', + TaxCategory::DigitalGoods(), + ), + ), + 20, + true, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/create_with_multiple_non_catalog_price_and_product'), + ]; + + yield 'Create with non catalog price (deprecated)' => [ + new CreateTransaction( + items: [ + new DeprecatedTransactionCreateItemWithPrice( + new DeprecatedTransactionNonCatalogPrice( 'Annual (per seat)', 'Annual (per seat)', new TimePeriod(Interval::Year(), 1), @@ -161,6 +252,36 @@ public static function createOperationsProvider(): \Generator self::readRawJsonFixture('request/create_with_non_catalog_price'), ]; + yield 'Create with non catalog price and product (deprecated)' => [ + new CreateTransaction( + items: [ + new DeprecatedTransactionCreateItemWithPrice( + new DeprecatedTransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + new Money('30000', CurrencyCode::USD()), + [], + new PriceQuantity(10, 999), + null, + new DeprecatedTransactionNonCatalogProduct( + 'Annual (per seat)', + 'Some description', + TaxCategory::DigitalGoods(), + 'https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png', + new CustomData([]), + ), + ), + 20, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/create_with_non_catalog_price_and_product'), + ]; + yield 'Create Manually Collected' => [ new CreateTransaction( items: [ @@ -212,6 +333,100 @@ public static function updateOperationsProvider(): \Generator self::readRawJsonFixture('request/update_single'), ]; + yield 'Update with catalog price' => [ + new UpdateTransaction( + status: TransactionStatus::Billed(), + items: [ + new TransactionUpdateItem( + 'pri_01he5kxqey1k8ankgef29cj4bv', + 1, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/full_entity')), + self::readRawJsonFixture('request/update_with_catalog_price'), + ]; + + yield 'Update with catalog price (deprecated)' => [ + new UpdateTransaction( + status: TransactionStatus::Billed(), + items: [ + new DeprecatedTransactionUpdateTransactionItem( + 'pri_01he5kxqey1k8ankgef29cj4bv', + 1, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/full_entity')), + self::readRawJsonFixture('request/update_with_catalog_price'), + ]; + + yield 'Update with non catalog price' => [ + new UpdateTransaction( + items: [ + new TransactionUpdateItemWithPrice( + new TransactionNonCatalogPrice( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + 'pro_01gsz4t5hdjse780zja8vvr7jg', + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + [], + new PriceQuantity(10, 999), + null, + ), + 20, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/update_with_non_catalog_price'), + ]; + + yield 'Update with non catalog price and product' => [ + new UpdateTransaction( + items: [ + new TransactionUpdateItemWithPrice( + new TransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + new TransactionNonCatalogProduct( + 'Annual (per seat)', + TaxCategory::DigitalGoods(), + 'Some description', + 'https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png', + new CustomData([]), + ), + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + [], + new PriceQuantity(10, 999), + null, + ), + 20, + ), + new TransactionItemPreviewWithNonCatalogPrice( + new TransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + new TransactionNonCatalogProduct( + 'Annual (per seat)', + TaxCategory::DigitalGoods(), + ), + ), + 20, + true, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/update_with_non_catalog_price_and_product'), + ]; + yield 'Update Partial' => [ new UpdateTransaction( status: TransactionStatus::Billed(), @@ -465,11 +680,132 @@ public static function previewOperationsProvider(): \Generator self::readRawJsonFixture('request/preview_basic'), ]; + yield 'Basic Preview (deprecated)' => [ + new PreviewTransaction( + items: [ + new DeprecatedTransactionItemPreviewWithPriceId('pri_01he5kxqey1k8ankgef29cj4bv', 1, true), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/preview_entity')), + self::readRawJsonFixture('request/preview_basic'), + ]; + yield 'Preview with non catalog price' => [ new PreviewTransaction( items: [ new TransactionItemPreviewWithNonCatalogPrice( new TransactionNonCatalogPrice( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + 'pro_01gsz4t5hdjse780zja8vvr7jg', + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + [], + new PriceQuantity(10, 999), + null, + ), + 20, + true, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/preview_entity')), + self::readRawJsonFixture('request/preview_with_non_catalog_price'), + ]; + + yield 'Preview with basic non catalog price' => [ + new PreviewTransaction( + items: [ + new TransactionItemPreviewWithNonCatalogPrice( + new TransactionNonCatalogPrice( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + 'pro_01gsz4t5hdjse780zja8vvr7jg', + ), + 20, + true, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/preview_entity')), + self::readRawJsonFixture('request/preview_with_basic_non_catalog_price'), + ]; + + yield 'Preview with non catalog price and product' => [ + new PreviewTransaction( + items: [ + new TransactionItemPreviewWithNonCatalogPrice( + new TransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + new TransactionNonCatalogProduct( + 'Annual (per seat)', + TaxCategory::DigitalGoods(), + 'Some description', + 'https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png', + new CustomData([]), + ), + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + [], + new PriceQuantity(10, 999), + null, + ), + 20, + true, + ), + new TransactionItemPreviewWithNonCatalogPrice( + new TransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + new Money('30000', CurrencyCode::USD()), + new TransactionNonCatalogProduct( + 'Annual (per seat)', + TaxCategory::DigitalGoods(), + ), + ), + 20, + true, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/preview_entity')), + self::readRawJsonFixture('request/preview_with_multiple_non_catalog_price_and_product'), + ]; + + yield 'Preview with non catalog price (deprecated)' => [ + new PreviewTransaction( + items: [ + new DeprecatedTransactionItemPreviewWithNonCatalogPrice( + new DeprecatedTransactionNonCatalogPrice( + 'Annual (per seat)', + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + new Money('30000', CurrencyCode::USD()), + [], + new PriceQuantity(10, 999), + null, + 'pro_01gsz4t5hdjse780zja8vvr7jg', + ), + 20, + true, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/preview_entity')), + self::readRawJsonFixture('request/preview_with_non_catalog_price'), + ]; + + yield 'Preview with non catalog price and product ID (deprecated)' => [ + new PreviewTransaction( + items: [ + new DeprecatedTransactionItemPreviewWithNonCatalogPrice( + new DeprecatedTransactionNonCatalogPrice( 'Annual (per seat)', 'Annual (per seat)', new TimePeriod(Interval::Year(), 1), @@ -489,6 +825,63 @@ public static function previewOperationsProvider(): \Generator new Response(200, body: self::readRawJsonFixture('response/preview_entity')), self::readRawJsonFixture('request/preview_with_non_catalog_price'), ]; + + yield 'Preview with non catalog price and product (deprecated)' => [ + new PreviewTransaction( + items: [ + new DeprecatedTransactionItemPreviewWithNonCatalogPrice( + new DeprecatedTransactionNonCatalogPriceWithProduct( + 'Annual (per seat)', + 'Annual (per seat)', + new TimePeriod(Interval::Year(), 1), + null, + TaxMode::AccountSetting(), + new Money('30000', CurrencyCode::USD()), + [], + new PriceQuantity(10, 999), + null, + new DeprecatedTransactionNonCatalogProduct( + 'Annual (per seat)', + 'Some description', + TaxCategory::DigitalGoods(), + 'https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png', + new CustomData([]), + ), + ), + 20, + true, + ), + ], + ), + new Response(200, body: self::readRawJsonFixture('response/preview_entity')), + self::readRawJsonFixture('request/preview_with_non_catalog_price_and_product'), + ]; + } + + /** + * @test + */ + public function it_has_prices_with_and_without_id_on_preview(): void + { + $this->mockClient->addResponse(new Response(200, body: self::readRawJsonFixture('response/preview_entity'))); + $preview = $this->client->transactions->preview( + new PreviewTransaction( + items: [ + new TransactionItemPreviewWithPriceId('pri_01gsz8z1q1n00f12qt82y31smh', 1, true), + ], + ), + ); + + $price = $preview->items[0]->price; + self::assertInstanceOf(Price::class, $price); + self::assertSame('pri_01gsz8z1q1n00f12qt82y31smh', $price->id); + + $transactionPreviewPrice = $preview->items[1]->price; + self::assertInstanceOf(TransactionPreviewPrice::class, $transactionPreviewPrice); + self::assertNull($transactionPreviewPrice->id); + + self::assertNull($preview->details->lineItems[0]->priceId); + self::assertSame('pri_01gsz8z1q1n00f12qt82y31smh', $preview->details->lineItems[1]->priceId); } /** diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/create_with_multiple_non_catalog_price_and_product.json b/tests/Functional/Resources/Transactions/_fixtures/request/create_with_multiple_non_catalog_price_and_product.json new file mode 100644 index 0000000..c652d39 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/create_with_multiple_non_catalog_price_and_product.json @@ -0,0 +1,49 @@ +{ + "items": [ + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "name": "Annual (per seat)", + "product": { + "custom_data": [], + "description": "Some description", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png", + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "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 + } + }, + { + "include_in_totals": true, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + }, + "quantity": 20 + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price_and_product.json b/tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price_and_product.json new file mode 100644 index 0000000..e665ce2 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/create_with_non_catalog_price_and_product.json @@ -0,0 +1,34 @@ +{ + "items": [ + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "name": "Annual (per seat)", + "product": { + "custom_data": [], + "description": "Some description", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png", + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "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 + } + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_basic_non_catalog_price.json b/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_basic_non_catalog_price.json new file mode 100644 index 0000000..8a057b4 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_basic_non_catalog_price.json @@ -0,0 +1,16 @@ +{ + "items": [ + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + }, + "include_in_totals": true + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_multiple_non_catalog_price_and_product.json b/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_multiple_non_catalog_price_and_product.json new file mode 100644 index 0000000..d43c8ef --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_multiple_non_catalog_price_and_product.json @@ -0,0 +1,50 @@ +{ + "items": [ + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "name": "Annual (per seat)", + "product": { + "custom_data": [], + "description": "Some description", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png", + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "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 + }, + "include_in_totals": true + }, + { + "include_in_totals": true, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + }, + "quantity": 20 + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_non_catalog_price_and_product.json b/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_non_catalog_price_and_product.json new file mode 100644 index 0000000..1473e76 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/preview_with_non_catalog_price_and_product.json @@ -0,0 +1,35 @@ +{ + "items": [ + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "name": "Annual (per seat)", + "product": { + "custom_data": [], + "description": "Some description", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png", + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "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 + }, + "include_in_totals": true + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/update_with_catalog_price.json b/tests/Functional/Resources/Transactions/_fixtures/request/update_with_catalog_price.json new file mode 100644 index 0000000..2a29f16 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/update_with_catalog_price.json @@ -0,0 +1,9 @@ +{ + "status": "billed", + "items": [ + { + "quantity": 1, + "price_id": "pri_01he5kxqey1k8ankgef29cj4bv" + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price.json b/tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price.json new file mode 100644 index 0000000..eb64ad5 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price.json @@ -0,0 +1,28 @@ +{ + "items": [ + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "name": "Annual (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "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 + } + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price_and_product.json b/tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price_and_product.json new file mode 100644 index 0000000..c652d39 --- /dev/null +++ b/tests/Functional/Resources/Transactions/_fixtures/request/update_with_non_catalog_price_and_product.json @@ -0,0 +1,49 @@ +{ + "items": [ + { + "quantity": 20, + "price": { + "description": "Annual (per seat)", + "name": "Annual (per seat)", + "product": { + "custom_data": [], + "description": "Some description", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png", + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "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 + } + }, + { + "include_in_totals": true, + "price": { + "description": "Annual (per seat)", + "product": { + "name": "Annual (per seat)", + "tax_category": "digital-goods" + }, + "unit_price": { + "amount": "30000", + "currency_code": "USD" + } + }, + "quantity": 20 + } + ] +} diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json b/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json index 977a7b9..e2f5562 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json @@ -43,7 +43,7 @@ }, { "price": { - "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "id": null, "description": "One-time charge", "name": "One-time charge", "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", @@ -104,7 +104,7 @@ }, "line_items": [ { - "price_id": "pri_01gsz8z1q1n00f12qt82y31smh", + "price_id": null, "quantity": 20, "totals": { "subtotal": "600000", @@ -132,7 +132,7 @@ } }, { - "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "price_id": "pri_01gsz8z1q1n00f12qt82y31smh", "quantity": 1, "totals": { "subtotal": "19900",