From af2dfb033846c1003835660a73303f49a9e7e84a Mon Sep 17 00:00:00 2001 From: davidgrayston-paddle Date: Fri, 13 Sep 2024 16:45:28 +0100 Subject: [PATCH] Feature: API Sync (#70) * Feature: Support disposition query parameter * Feature: Support custom prices when updating and previewing subscriptions * Fix: Add validation for pricing preview items * Feature: Support notification settings pagination and active filter --- CHANGELOG.md | 8 ++ src/Entities/Shared/Disposition.php | 24 ++++ .../SubscriptionNonCatalogPrice.php | 3 + ...SubscriptionNonCatalogPriceWithProduct.php | 3 + .../InvalidArgumentException.php | 5 + .../NotificationSettingsClient.php | 7 +- .../Operations/ListNotificationSettings.php | 27 ++++ .../Operations/PreviewPrice.php | 8 ++ .../Operations/PreviewUpdateSubscription.php | 3 +- .../Operations/UpdateSubscription.php | 3 +- .../Operations/GetTransactionInvoice.php | 23 ++++ .../Transactions/TransactionsClient.php | 5 +- .../NotificationSettingsClientTest.php | 94 +++++++++++++- .../_fixtures/response/list_default.json | 8 +- .../response/list_paginated_page_one.json | 115 ++++++++++++++++++ .../response/list_paginated_page_two.json | 115 ++++++++++++++++++ .../PricingPreviewsClientTest.php | 2 +- .../Subscriptions/SubscriptionsClientTest.php | 85 +++++++++++++ .../request/preview_update_full.json | 69 ++++++++++- .../_fixtures/request/update_full.json | 69 ++++++++++- .../Transactions/TransactionsClientTest.php | 22 +++- .../Operations/PreviewPriceTest.php | 42 +++++++ 22 files changed, 725 insertions(+), 15 deletions(-) create mode 100644 src/Entities/Shared/Disposition.php create mode 100644 src/Resources/NotificationSettings/Operations/ListNotificationSettings.php create mode 100644 src/Resources/Transactions/Operations/GetTransactionInvoice.php create mode 100644 tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_one.json create mode 100644 tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_two.json create mode 100644 tests/Unit/Resources/PricingPreviews/Operations/PreviewPriceTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f33e6ea..399dc52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx - Added `product` to `subscription.items[]`, see [related changelog](https://developer.paddle.com/changelog/2024/subscription-items-product?utm_source=dx&utm_medium=paddle-php-sdk) - Added `import_meta` to `transaction` - Support for `createdAt` and `updatedAt` on Subscription notification prices +- Support custom prices when updating and previewing subscriptions, see [related changelog](https://developer.paddle.com/changelog/2024/add-custom-items-subscription) +- `TransactionsClient::getInvoicePDF` now supports `disposition` parameter, see [related changelog](https://developer.paddle.com/changelog/2024/invoice-pdf-open-in-browser) +- Support notification settings pagination, see [related changelog](https://developer.paddle.com/changelog/2024/notification-settings-pagination) +- Support notification settings `active` filter + +### Fixed + +- `PreviewPrice` operation no longer allows empty `items` ## [1.1.2] - 2024-08-23 diff --git a/src/Entities/Shared/Disposition.php b/src/Entities/Shared/Disposition.php new file mode 100644 index 0000000..0f97155 --- /dev/null +++ b/src/Entities/Shared/Disposition.php @@ -0,0 +1,24 @@ +client->getRaw('notification-settings'), + $this->client->getRaw('notification-settings', $listOperation), ); return NotificationSettingCollection::from( $parser->getData(), + new Paginator($this->client, $parser->getPagination(), NotificationSettingCollection::class), ); } diff --git a/src/Resources/NotificationSettings/Operations/ListNotificationSettings.php b/src/Resources/NotificationSettings/Operations/ListNotificationSettings.php new file mode 100644 index 0000000..84c4bae --- /dev/null +++ b/src/Resources/NotificationSettings/Operations/ListNotificationSettings.php @@ -0,0 +1,27 @@ +pager?->getParameters() ?? [], + array_filter([ + 'active' => isset($this->active) ? ($this->active ? 'true' : 'false') : null, + ]), + ); + } +} diff --git a/src/Resources/PricingPreviews/Operations/PreviewPrice.php b/src/Resources/PricingPreviews/Operations/PreviewPrice.php index 31773cb..24a9ac7 100644 --- a/src/Resources/PricingPreviews/Operations/PreviewPrice.php +++ b/src/Resources/PricingPreviews/Operations/PreviewPrice.php @@ -7,6 +7,7 @@ use Paddle\SDK\Entities\PricingPreview\PricePreviewItem; use Paddle\SDK\Entities\Shared\AddressPreview; use Paddle\SDK\Entities\Shared\CurrencyCode; +use Paddle\SDK\Exceptions\SdkExceptions\InvalidArgumentException; use Paddle\SDK\FiltersUndefined; use Paddle\SDK\Undefined; @@ -27,6 +28,13 @@ public function __construct( public readonly AddressPreview|Undefined|null $address = new Undefined(), public readonly string|Undefined|null $customerIpAddress = new Undefined(), ) { + if (count($this->items) === 0) { + throw InvalidArgumentException::arrayIsEmpty('items'); + } + + if ($invalid = array_filter($this->items, fn ($value): bool => ! $value instanceof PricePreviewItem)) { + throw InvalidArgumentException::arrayContainsInvalidTypes('items', PricePreviewItem::class, implode(', ', $invalid)); + } } public function jsonSerialize(): array diff --git a/src/Resources/Subscriptions/Operations/PreviewUpdateSubscription.php b/src/Resources/Subscriptions/Operations/PreviewUpdateSubscription.php index fb7c6e8..540a21d 100644 --- a/src/Resources/Subscriptions/Operations/PreviewUpdateSubscription.php +++ b/src/Resources/Subscriptions/Operations/PreviewUpdateSubscription.php @@ -10,6 +10,7 @@ use Paddle\SDK\Entities\Shared\CurrencyCode; use Paddle\SDK\Entities\Shared\CustomData; use Paddle\SDK\Entities\Subscription\SubscriptionItems; +use Paddle\SDK\Entities\Subscription\SubscriptionItemsWithPrice; use Paddle\SDK\Entities\Subscription\SubscriptionOnPaymentFailure; use Paddle\SDK\Entities\Subscription\SubscriptionProrationBillingMode; use Paddle\SDK\FiltersUndefined; @@ -21,7 +22,7 @@ class PreviewUpdateSubscription implements \JsonSerializable use FiltersUndefined; /** - * @param array $items + * @param array $items */ public function __construct( public readonly string|Undefined $customerId = new Undefined(), diff --git a/src/Resources/Subscriptions/Operations/UpdateSubscription.php b/src/Resources/Subscriptions/Operations/UpdateSubscription.php index f332e8e..dbf3f76 100644 --- a/src/Resources/Subscriptions/Operations/UpdateSubscription.php +++ b/src/Resources/Subscriptions/Operations/UpdateSubscription.php @@ -10,6 +10,7 @@ use Paddle\SDK\Entities\Shared\CurrencyCode; use Paddle\SDK\Entities\Shared\CustomData; use Paddle\SDK\Entities\Subscription\SubscriptionItems; +use Paddle\SDK\Entities\Subscription\SubscriptionItemsWithPrice; use Paddle\SDK\Entities\Subscription\SubscriptionOnPaymentFailure; use Paddle\SDK\Entities\Subscription\SubscriptionProrationBillingMode; use Paddle\SDK\FiltersUndefined; @@ -21,7 +22,7 @@ class UpdateSubscription implements \JsonSerializable use FiltersUndefined; /** - * @param array $items + * @param array $items */ public function __construct( public readonly string|Undefined $customerId = new Undefined(), diff --git a/src/Resources/Transactions/Operations/GetTransactionInvoice.php b/src/Resources/Transactions/Operations/GetTransactionInvoice.php new file mode 100644 index 0000000..2041484 --- /dev/null +++ b/src/Resources/Transactions/Operations/GetTransactionInvoice.php @@ -0,0 +1,23 @@ + $this->disposition?->getValue(), + ]); + } +} diff --git a/src/Resources/Transactions/TransactionsClient.php b/src/Resources/Transactions/TransactionsClient.php index c7bb398..5b9e588 100644 --- a/src/Resources/Transactions/TransactionsClient.php +++ b/src/Resources/Transactions/TransactionsClient.php @@ -21,6 +21,7 @@ use Paddle\SDK\Exceptions\SdkExceptions\InvalidArgumentException; use Paddle\SDK\Exceptions\SdkExceptions\MalformedResponse; 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\ListTransactions; use Paddle\SDK\Resources\Transactions\Operations\PreviewTransaction; @@ -128,10 +129,10 @@ public function preview(PreviewTransaction $operation): TransactionPreview * @throws ApiError\TransactionApiError On a transaction specific API error * @throws MalformedResponse If the API response was not parsable */ - public function getInvoicePDF(string $id): TransactionData + public function getInvoicePDF(string $id, GetTransactionInvoice $getOperation = new GetTransactionInvoice()): TransactionData { $parser = new ResponseParser( - $this->client->getRaw("/transactions/{$id}/invoice"), + $this->client->getRaw("/transactions/{$id}/invoice", $getOperation), ); return TransactionData::from($parser->getData()); diff --git a/tests/Functional/Resources/NotificationSettings/NotificationSettingsClientTest.php b/tests/Functional/Resources/NotificationSettings/NotificationSettingsClientTest.php index d1074a7..bbbc5b2 100644 --- a/tests/Functional/Resources/NotificationSettings/NotificationSettingsClientTest.php +++ b/tests/Functional/Resources/NotificationSettings/NotificationSettingsClientTest.php @@ -12,7 +12,10 @@ use Paddle\SDK\Environment; use Paddle\SDK\Options; use Paddle\SDK\Resources\NotificationSettings\Operations\CreateNotificationSetting; +use Paddle\SDK\Resources\NotificationSettings\Operations\ListNotificationSettings; use Paddle\SDK\Resources\NotificationSettings\Operations\UpdateNotificationSetting; +use Paddle\SDK\Resources\Shared\Operations\List\OrderBy; +use Paddle\SDK\Resources\Shared\Operations\List\Pager; use Paddle\SDK\Tests\Utils\ReadsFixtures; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; @@ -173,11 +176,11 @@ public static function updateOperationsProvider(): \Generator * @dataProvider listOperationsProvider */ public function list_hits_expected_uri( - ResponseInterface $response, + ListNotificationSettings $listOperation, string $expectedUri, ): void { - $this->mockClient->addResponse($response); - $this->client->notificationSettings->list(); + $this->mockClient->addResponse(new Response(200, body: self::readRawJsonFixture('response/list_default'))); + $this->client->notificationSettings->list($listOperation); $request = $this->mockClient->getLastRequest(); self::assertInstanceOf(RequestInterface::class, $request); @@ -188,9 +191,92 @@ public function list_hits_expected_uri( public static function listOperationsProvider(): \Generator { yield 'Default' => [ - new Response(200, body: self::readRawJsonFixture('response/list_default')), + new ListNotificationSettings(), sprintf('%s/notification-settings', Environment::SANDBOX->baseUrl()), ]; + + yield 'With active filter true' => [ + new ListNotificationSettings(null, true), + sprintf('%s/notification-settings?active=true', Environment::SANDBOX->baseUrl()), + ]; + + yield 'With active filter false' => [ + new ListNotificationSettings(null, false), + sprintf('%s/notification-settings?active=false', Environment::SANDBOX->baseUrl()), + ]; + + yield 'With default pagination' => [ + new ListNotificationSettings( + new Pager(), + ), + sprintf('%s/notification-settings?order_by=id[asc]&per_page=50', Environment::SANDBOX->baseUrl()), + ]; + + yield 'With pagination after' => [ + new ListNotificationSettings( + new Pager('ntfset_01gkpjp8bkm3tm53kdgkx6sms7'), + ), + sprintf( + '%s/notification-settings?after=ntfset_01gkpjp8bkm3tm53kdgkx6sms7&order_by=id[asc]&per_page=50', + Environment::SANDBOX->baseUrl(), + ), + ]; + + yield 'With pagination after, order by ID asc' => [ + new ListNotificationSettings( + new Pager('ntfset_02gkpjp8bkm3tm53kdgkx6sms7', OrderBy::idAscending()), + ), + sprintf( + '%s/notification-settings?after=ntfset_02gkpjp8bkm3tm53kdgkx6sms7&order_by=id[asc]&per_page=50', + Environment::SANDBOX->baseUrl(), + ), + ]; + + yield 'With pagination after, order by ID desc' => [ + new ListNotificationSettings( + new Pager('ntfset_03gkpjp8bkm3tm53kdgkx6sms7', OrderBy::idDescending()), + ), + sprintf( + '%s/notification-settings?after=ntfset_03gkpjp8bkm3tm53kdgkx6sms7&order_by=id[desc]&per_page=50', + Environment::SANDBOX->baseUrl(), + ), + ]; + + yield 'With pagination after, order by ID asc, per page' => [ + new ListNotificationSettings( + new Pager('ntfset_04gkpjp8bkm3tm53kdgkx6sms7', OrderBy::idDescending(), 10), + ), + sprintf( + '%s/notification-settings?after=ntfset_04gkpjp8bkm3tm53kdgkx6sms7&order_by=id[desc]&per_page=10', + Environment::SANDBOX->baseUrl(), + ), + ]; + } + + /** @test */ + public function it_can_paginate(): void + { + $this->mockClient->addResponse(new Response(200, body: self::readRawJsonFixture('response/list_paginated_page_one'))); + $this->mockClient->addResponse(new Response(200, body: self::readRawJsonFixture('response/list_paginated_page_two'))); + + $collection = $this->client->notificationSettings->list(); + + $request = $this->mockClient->getLastRequest(); + + self::assertEquals( + Environment::SANDBOX->baseUrl() . '/notification-settings', + urldecode((string) $request->getUri()), + ); + + $allNotificationSettings = iterator_to_array($collection); + self::assertCount(2, $allNotificationSettings); + + $request = $this->mockClient->getLastRequest(); + + self::assertEquals( + Environment::SANDBOX->baseUrl() . '/notification-settings?after=ntfset_01gkpjp8bkm3tm53kdgkx6sms7', + urldecode((string) $request->getUri()), + ); } /** diff --git a/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_default.json b/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_default.json index 56b237e..9ecfc1f 100644 --- a/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_default.json +++ b/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_default.json @@ -206,6 +206,12 @@ } ], "meta": { - "request_id": "cf039cbb-6c2a-485d-b244-501894b797a6" + "request_id": "cf039cbb-6c2a-485d-b244-501894b797a6", + "pagination": { + "per_page": 5, + "next": "https://api.paddle.com/notification-settings?after=ntfset_01gkpop8bkm3tm53itgkx6klk7", + "has_more": false, + "estimated_total": 5 + } } } diff --git a/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_one.json b/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_one.json new file mode 100644 index 0000000..f5765ad --- /dev/null +++ b/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_one.json @@ -0,0 +1,115 @@ +{ + "data": [ + { + "id": "ntfset_01gkpjp8bkm3tm53kdgkx6sms7", + "description": "Slack notifications", + "type": "url", + "destination": "https://hooks.slack.com/example", + "active": true, + "api_version": 1, + "include_sensitive_fields": false, + "subscribed_events": [ + { + "name": "transaction.billed", + "description": "Occurs when a transaction is billed.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.canceled", + "description": "Occurs when a transaction is canceled.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.completed", + "description": "Occurs when a transaction is completed.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.created", + "description": "Occurs when a transaction is created.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.payment_failed", + "description": "Occurs when a payment fails for a transaction.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.ready", + "description": "Occurs when a transaction is ready.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.updated", + "description": "Occurs when a transaction is updated.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "subscription.activated", + "description": "Occurs when a subscription is activated.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.canceled", + "description": "Occurs when a subscription is canceled.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.created", + "description": "Occurs when a subscription is created.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.past_due", + "description": "Occurs when a subscription is past due.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.paused", + "description": "Occurs when a subscription is paused.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.resumed", + "description": "Occurs when a subscription is resumed.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.trialing", + "description": "Occurs when a subscription is trialing.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.updated", + "description": "Occurs when a subscription is updated.", + "group": "Subscription", + "available_versions": [1] + } + ], + "endpoint_secret_key": "pdl_ntfset_01gkpjp8bkm3tm53kdgkx6sms7_6h3qd3uFSi9YCD3OLYAShQI90XTI5vEI" + } + ], + "meta": { + "request_id": "cf039cbb-6c2a-485d-b244-501894b797a6", + "pagination": { + "per_page": 1, + "next": "https://api.paddle.com/notification-settings?after=ntfset_01gkpjp8bkm3tm53kdgkx6sms7", + "has_more": true, + "estimated_total": 2 + } + } +} diff --git a/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_two.json b/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_two.json new file mode 100644 index 0000000..fb1e866 --- /dev/null +++ b/tests/Functional/Resources/NotificationSettings/_fixtures/response/list_paginated_page_two.json @@ -0,0 +1,115 @@ +{ + "data": [ + { + "id": "ntfset_01gkpop8bkm3tm53itgkx6klk7", + "description": "Discord notifications", + "type": "url", + "destination": "https://hooks.discord.com/example", + "active": true, + "api_version": 1, + "include_sensitive_fields": false, + "subscribed_events": [ + { + "name": "transaction.billed", + "description": "Occurs when a transaction is billed.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.canceled", + "description": "Occurs when a transaction is canceled.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.completed", + "description": "Occurs when a transaction is completed.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.created", + "description": "Occurs when a transaction is created.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.payment_failed", + "description": "Occurs when a payment fails for a transaction.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.ready", + "description": "Occurs when a transaction is ready.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "transaction.updated", + "description": "Occurs when a transaction is updated.", + "group": "Transaction", + "available_versions": [1] + }, + { + "name": "subscription.activated", + "description": "Occurs when a subscription is activated.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.canceled", + "description": "Occurs when a subscription is canceled.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.created", + "description": "Occurs when a subscription is created.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.past_due", + "description": "Occurs when a subscription is past due.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.paused", + "description": "Occurs when a subscription is paused.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.resumed", + "description": "Occurs when a subscription is resumed.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.trialing", + "description": "Occurs when a subscription is trialing.", + "group": "Subscription", + "available_versions": [1] + }, + { + "name": "subscription.updated", + "description": "Occurs when a subscription is updated.", + "group": "Subscription", + "available_versions": [1] + } + ], + "endpoint_secret_key": "ntfset_01gkpop8bkm3tm53itgkx6klk7_6h3qd3uFSi9YCD3OLYAShQI90XTI5vEI" + } + ], + "meta": { + "request_id": "cf039cbb-6c2a-485d-b244-501894b797a6", + "pagination": { + "per_page": 1, + "next": "https://api.paddle.com/notification-settings?after=ntfset_01gkpop8bkm3tm53itgkx6klk7", + "has_more": false, + "estimated_total": 2 + } + } +} diff --git a/tests/Functional/Resources/PricingPreviews/PricingPreviewsClientTest.php b/tests/Functional/Resources/PricingPreviews/PricingPreviewsClientTest.php index 74cb4f8..ff33b7a 100644 --- a/tests/Functional/Resources/PricingPreviews/PricingPreviewsClientTest.php +++ b/tests/Functional/Resources/PricingPreviews/PricingPreviewsClientTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Paddle\SDK\Tests\Functional\Resources\Prices; +namespace Paddle\SDK\Tests\Functional\Resources\PricingPreviews; use GuzzleHttp\Psr7\Response; use Http\Mock\Client as MockClient; diff --git a/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php b/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php index a916be3..22a4184 100644 --- a/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php +++ b/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php @@ -7,11 +7,22 @@ use GuzzleHttp\Psr7\Response; use Http\Mock\Client as MockClient; use Paddle\SDK\Client; +use Paddle\SDK\Entities\Shared\CatalogType; use Paddle\SDK\Entities\Shared\CollectionMode; use Paddle\SDK\Entities\Shared\CurrencyCode; use Paddle\SDK\Entities\Shared\CustomData; +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\Subscription\SubscriptionEffectiveFrom; use Paddle\SDK\Entities\Subscription\SubscriptionItems; +use Paddle\SDK\Entities\Subscription\SubscriptionItemsWithPrice; +use Paddle\SDK\Entities\Subscription\SubscriptionNonCatalogPrice; +use Paddle\SDK\Entities\Subscription\SubscriptionNonCatalogPriceWithProduct; +use Paddle\SDK\Entities\Subscription\SubscriptionNonCatalogProduct; use Paddle\SDK\Entities\Subscription\SubscriptionOnPaymentFailure; use Paddle\SDK\Entities\Subscription\SubscriptionProrationBillingMode; use Paddle\SDK\Entities\Subscription\SubscriptionResumeEffectiveFrom; @@ -105,6 +116,43 @@ public static function updateOperationsProvider(): \Generator items: [ new SubscriptionItems('pri_01gsz91wy9k1yn7kx82aafwvea', 1), new SubscriptionItems('pri_01gsz91wy9k1yn7kx82bafwvea', 5), + new SubscriptionItemsWithPrice( + new SubscriptionNonCatalogPrice( + 'some description', + 'some name', + 'pro_01gsz4t5hdjse780zja8vvr7jg', + TaxMode::AccountSetting(), + new Money('1', CurrencyCode::GBP()), + [], + new PriceQuantity(1, 3), + new CustomData(['key' => 'value']), + new TimePeriod(Interval::Day(), 1), + new TimePeriod(Interval::Day(), 2), + ), + 2, + ), + new SubscriptionItemsWithPrice( + new SubscriptionNonCatalogPriceWithProduct( + 'some description', + 'some name', + new SubscriptionNonCatalogProduct( + 'some name', + 'some description', + CatalogType::Custom(), + TaxCategory::DigitalGoods(), + 'https://www.example.com/image.jpg', + new CustomData(['key' => 'value']), + ), + TaxMode::AccountSetting(), + new Money('1', CurrencyCode::GBP()), + [], + new PriceQuantity(1, 3), + new CustomData(['key' => 'value']), + new TimePeriod(Interval::Day(), 1), + new TimePeriod(Interval::Day(), 2), + ), + 2, + ), ], customData: new CustomData(['early_access' => true]), prorationBillingMode: SubscriptionProrationBillingMode::FullImmediately(), @@ -575,6 +623,43 @@ public static function previewUpdateOperationsProvider(): \Generator items: [ new SubscriptionItems('pri_01gsz91wy9k1yn7kx82aafwvea', 1), new SubscriptionItems('pri_01gsz91wy9k1yn7kx82bafwvea', 5), + new SubscriptionItemsWithPrice( + new SubscriptionNonCatalogPrice( + 'some description', + 'some name', + 'pro_01gsz4t5hdjse780zja8vvr7jg', + TaxMode::AccountSetting(), + new Money('1', CurrencyCode::GBP()), + [], + new PriceQuantity(1, 3), + new CustomData(['key' => 'value']), + new TimePeriod(Interval::Day(), 1), + new TimePeriod(Interval::Day(), 2), + ), + 2, + ), + new SubscriptionItemsWithPrice( + new SubscriptionNonCatalogPriceWithProduct( + 'some description', + 'some name', + new SubscriptionNonCatalogProduct( + 'some name', + 'some description', + CatalogType::Custom(), + TaxCategory::DigitalGoods(), + 'https://www.example.com/image.jpg', + new CustomData(['key' => 'value']), + ), + TaxMode::AccountSetting(), + new Money('1', CurrencyCode::GBP()), + [], + new PriceQuantity(1, 3), + new CustomData(['key' => 'value']), + new TimePeriod(Interval::Day(), 1), + new TimePeriod(Interval::Day(), 2), + ), + 2, + ), ], customData: new CustomData(['early_access' => true]), prorationBillingMode: SubscriptionProrationBillingMode::FullImmediately(), 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 0201ca7..a1929b8 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,74 @@ "scheduled_change": null, "items": [ { "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", "quantity": 1 }, - { "price_id": "pri_01gsz91wy9k1yn7kx82bafwvea", "quantity": 5 } + { "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", + "type": "custom" + }, + "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 + } ], "proration_billing_mode": "full_immediately", "custom_data": { diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json b/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json index 0201ca7..a1929b8 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/request/update_full.json @@ -13,7 +13,74 @@ "scheduled_change": null, "items": [ { "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", "quantity": 1 }, - { "price_id": "pri_01gsz91wy9k1yn7kx82bafwvea", "quantity": 5 } + { "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", + "type": "custom" + }, + "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 + } ], "proration_billing_mode": "full_immediately", "custom_data": { diff --git a/tests/Functional/Resources/Transactions/TransactionsClientTest.php b/tests/Functional/Resources/Transactions/TransactionsClientTest.php index 2f04d16..64802b4 100644 --- a/tests/Functional/Resources/Transactions/TransactionsClientTest.php +++ b/tests/Functional/Resources/Transactions/TransactionsClientTest.php @@ -11,6 +11,7 @@ use Paddle\SDK\Entities\Shared\CollectionMode; use Paddle\SDK\Entities\Shared\CurrencyCode; use Paddle\SDK\Entities\Shared\CustomData; +use Paddle\SDK\Entities\Shared\Disposition; use Paddle\SDK\Entities\Shared\Interval; use Paddle\SDK\Entities\Shared\Money; use Paddle\SDK\Entities\Shared\PriceQuantity; @@ -29,6 +30,7 @@ use Paddle\SDK\Resources\Shared\Operations\List\DateComparison; use Paddle\SDK\Resources\Shared\Operations\List\Pager; 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; @@ -529,11 +531,13 @@ public static function previewOperationsProvider(): \Generator * @dataProvider getInvoicePDFOperationsProvider */ public function get_invoice_pdf_hits_expected_uri( + string $id, + GetTransactionInvoice $getOperation, ResponseInterface $response, string $expectedUri, ): void { $this->mockClient->addResponse($response); - $this->client->transactions->getInvoicePDF('txn_01hen7bxc1p8ep4yk7n5jbzk9r'); + $this->client->transactions->getInvoicePDF($id, $getOperation); $request = $this->mockClient->getLastRequest(); self::assertInstanceOf(RequestInterface::class, $request); @@ -544,8 +548,24 @@ public function get_invoice_pdf_hits_expected_uri( public static function getInvoicePDFOperationsProvider(): \Generator { yield 'Default' => [ + 'txn_01hen7bxc1p8ep4yk7n5jbzk9r', + new GetTransactionInvoice(), new Response(200, body: self::readRawJsonFixture('response/get_invoice_pdf_default')), sprintf('%s/transactions/txn_01hen7bxc1p8ep4yk7n5jbzk9r/invoice', Environment::SANDBOX->baseUrl()), ]; + + yield 'Disposition Inline' => [ + 'txn_02hen7bxc1p8ep4yk7n5jbzk9r', + new GetTransactionInvoice(Disposition::Inline()), + new Response(200, body: self::readRawJsonFixture('response/get_invoice_pdf_default')), + sprintf('%s/transactions/txn_02hen7bxc1p8ep4yk7n5jbzk9r/invoice?disposition=inline', Environment::SANDBOX->baseUrl()), + ]; + + yield 'Disposition Attachment' => [ + 'txn_03hen7bxc1p8ep4yk7n5jbzk9r', + new GetTransactionInvoice(Disposition::Attachment()), + new Response(200, body: self::readRawJsonFixture('response/get_invoice_pdf_default')), + sprintf('%s/transactions/txn_03hen7bxc1p8ep4yk7n5jbzk9r/invoice?disposition=attachment', Environment::SANDBOX->baseUrl()), + ]; } } diff --git a/tests/Unit/Resources/PricingPreviews/Operations/PreviewPriceTest.php b/tests/Unit/Resources/PricingPreviews/Operations/PreviewPriceTest.php new file mode 100644 index 0000000..7ab3d7c --- /dev/null +++ b/tests/Unit/Resources/PricingPreviews/Operations/PreviewPriceTest.php @@ -0,0 +1,42 @@ + [ + [], + 'items cannot be empty', + ]; + + yield 'Invalid Types' => [ + ['some string', new PricePreviewItem('pri_01gsz8z1q1n00f12qt82y31smh', 20), 123], + sprintf( + 'expected items to only contain only type/s %s, some string, 123 given', + PricePreviewItem::class, + ), + ]; + } +}