diff --git a/CHANGELOG.md b/CHANGELOG.md index b35678d..08322fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx ### Fixed - `Client->notifications->replay()` now calls the correct endpoint +- Subscription transaction line items now include proration (`nextTransaction`, `recurringTransactionDetails`, `immediateTransaction`) +- Transaction preview line items now include proration ## [1.5.0] - 2024-11-18 diff --git a/src/Entities/Shared/TransactionLineItemPreview.php b/src/Entities/Shared/TransactionLineItemPreview.php index 642a31b..7db33e4 100644 --- a/src/Entities/Shared/TransactionLineItemPreview.php +++ b/src/Entities/Shared/TransactionLineItemPreview.php @@ -13,6 +13,7 @@ use Paddle\SDK\Entities\Product; use Paddle\SDK\Entities\Transaction\TransactionPreviewProduct; +use Paddle\SDK\Entities\Transaction\TransactionProration; class TransactionLineItemPreview { @@ -23,6 +24,7 @@ private function __construct( public UnitTotals $unitTotals, public Totals $totals, public Product|TransactionPreviewProduct $product, + public TransactionProration|null $proration, ) { } @@ -37,6 +39,7 @@ public static function from(array $data): self isset($data['product']['id']) ? Product::from($data['product']) : TransactionPreviewProduct::from($data['product']), + isset($data['proration']) ? TransactionProration::from($data['proration']) : null, ); } } diff --git a/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php b/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php index 26c5bb3..034eb53 100644 --- a/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php +++ b/tests/Functional/Resources/Subscriptions/SubscriptionsClientTest.php @@ -683,7 +683,7 @@ public static function previewOneTimeChargeOperationsProvider(): \Generator new SubscriptionItems('pri_01gsz98e27ak2tyhexptwc58yk', 1), ], ), - new Response(200, body: self::readRawJsonFixture('response/preview_update_full_entity')), + new Response(200, body: self::readRawJsonFixture('response/preview_charge_full_entity')), self::readRawJsonFixture('request/preview_one_time_charge_minimal'), ]; @@ -696,8 +696,106 @@ public static function previewOneTimeChargeOperationsProvider(): \Generator new SubscriptionItems('pri_01h7zd9mzfq79850w4ryc39v38', 845), ], ), - new Response(200, body: self::readRawJsonFixture('response/preview_update_full_entity')), + new Response(200, body: self::readRawJsonFixture('response/preview_charge_full_entity')), self::readRawJsonFixture('request/preview_one_time_charge_full'), ]; } + + /** + * @test + */ + public function get_with_includes_returns_nullable_proration(): void + { + $this->mockClient->addResponse(new Response(200, body: self::readRawJsonFixture('response/full_entity_with_includes'))); + $subscription = $this->client->subscriptions->get('sub_01h8bx8fmywym11t6swgzba704'); + + $recurringTransactionProration = $subscription->recurringTransactionDetails->lineItems[0]->proration; + self::assertNotNull($recurringTransactionProration); + self::assertEquals('1', $recurringTransactionProration->rate); + self::assertEquals( + '2024-02-08T11:02:03+00:00', + $recurringTransactionProration->billingPeriod->startsAt->format(DATE_RFC3339), + ); + self::assertEquals( + '2024-03-08T11:02:03+00:00', + $recurringTransactionProration->billingPeriod->endsAt->format(DATE_RFC3339), + ); + + $nullRecurringTransactionProration = $subscription->recurringTransactionDetails->lineItems[1]->proration; + self::assertNull($nullRecurringTransactionProration); + + $nextTransactionProration = $subscription->nextTransaction->details->lineItems[0]->proration; + self::assertNotNull($nextTransactionProration); + self::assertEquals('1', $nextTransactionProration->rate); + self::assertEquals( + '2023-12-03T16:38:53+00:00', + $nextTransactionProration->billingPeriod->startsAt->format(DATE_RFC3339), + ); + self::assertEquals( + '2024-01-03T16:38:53+00:00', + $nextTransactionProration->billingPeriod->endsAt->format(DATE_RFC3339), + ); + + $nullNextTransactionProration = $subscription->nextTransaction->details->lineItems[1]->proration; + self::assertNull($nullNextTransactionProration); + } + + /** + * @test + */ + public function preview_returns_nullable_proration(): void + { + $this->mockClient->addResponse(new Response(200, body: self::readRawJsonFixture('response/preview_update_full_entity'))); + $subscriptionPreview = $this->client->subscriptions->previewUpdate( + 'sub_01h8bx8fmywym11t6swgzba704', + new PreviewUpdateSubscription( + prorationBillingMode: SubscriptionProrationBillingMode::ProratedNextBillingPeriod(), + ), + ); + + $recurringTransactionProration = $subscriptionPreview->recurringTransactionDetails->lineItems[0]->proration; + self::assertNotNull($recurringTransactionProration); + self::assertEquals('1', $recurringTransactionProration->rate); + self::assertEquals( + '2024-02-08T11:02:03+00:00', + $recurringTransactionProration->billingPeriod->startsAt->format(DATE_RFC3339), + ); + self::assertEquals( + '2024-03-08T11:02:03+00:00', + $recurringTransactionProration->billingPeriod->endsAt->format(DATE_RFC3339), + ); + + $nullRecurringTransactionProration = $subscriptionPreview->recurringTransactionDetails->lineItems[1]->proration; + self::assertNull($nullRecurringTransactionProration); + + $nextTransactionProration = $subscriptionPreview->nextTransaction->details->lineItems[0]->proration; + self::assertNotNull($nextTransactionProration); + self::assertEquals('1', $nextTransactionProration->rate); + self::assertEquals( + '2024-03-08T11:02:03+00:00', + $nextTransactionProration->billingPeriod->startsAt->format(DATE_RFC3339), + ); + self::assertEquals( + '2024-04-08T11:02:03+00:00', + $nextTransactionProration->billingPeriod->endsAt->format(DATE_RFC3339), + ); + + $nullNextTransactionProration = $subscriptionPreview->nextTransaction->details->lineItems[1]->proration; + self::assertNull($nullNextTransactionProration); + + $immediateTransactionProration = $subscriptionPreview->immediateTransaction->details->lineItems[0]->proration; + self::assertNotNull($immediateTransactionProration); + self::assertEquals('0.99993', $immediateTransactionProration->rate); + self::assertEquals( + '2024-02-08T11:05:53+00:00', + $immediateTransactionProration->billingPeriod->startsAt->format(DATE_RFC3339), + ); + self::assertEquals( + '2024-03-08T11:02:03+00:00', + $immediateTransactionProration->billingPeriod->endsAt->format(DATE_RFC3339), + ); + + $nullImmediateTransactionProration = $subscriptionPreview->immediateTransaction->details->lineItems[1]->proration; + self::assertNull($nullImmediateTransactionProration); + } } diff --git a/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json b/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json index 06b0d88..034ba66 100644 --- a/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json +++ b/tests/Functional/Resources/Subscriptions/_fixtures/response/full_entity_with_includes.json @@ -117,13 +117,7 @@ "tax": "500", "total": "3000" }, - "proration": { - "rate": "1", - "billing_period": { - "starts_at": "2023-12-03T16:38:53.111897Z", - "ends_at": "2024-01-03T16:38:53.111897Z" - } - } + "proration": null } ] }, @@ -160,6 +154,116 @@ } ] }, + "recurring_transaction_details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "178500", + "discount": "0", + "tax": "15841", + "total": "194341" + } + } + ], + "totals": { + "subtotal": "178500", + "tax": "15841", + "discount": "0", + "total": "194341", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "194341", + "grand_total": "194341", + "earnings": null, + "currency_code": "USD", + "exchange_rate": "1" + }, + "line_items": [ + { + "item_id": null, + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 50, + "totals": { + "subtotal": "150000", + "tax": "13312", + "discount": "0", + "total": "163312" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "ChatApp Pro", + "type": "standard", + "tax_category": "standard", + "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.", + "image_url": "https://paddle-sandbox.s3.amazonaws.com/user/10889/2nmP8MQSret0aWeDemRw_icon1.png", + "custom_data": { + "features": { + "crm": true, + "data_retention": false, + "reports": true + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of advanced reporting and a CRM that's right where you're chatting." + }, + "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": "3000", + "discount": "0", + "tax": "266", + "total": "3266" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-02-08T11:02:03.946454Z", + "ends_at": "2024-03-08T11:02:03.946454Z" + } + } + }, + { + "item_id": null, + "price_id": "pri_01gsz95g2zrkagg294kpstx54r", + "quantity": 1, + "totals": { + "subtotal": "28500", + "tax": "2529", + "discount": "0", + "total": "31029" + }, + "product": { + "id": "pro_01gsz92krfzy3hcx5h5rtgnfwz", + "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": null + } + ] + }, "scheduled_change": null, "items": [ { 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 75bbc12..ee9c97a 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 @@ -128,13 +128,7 @@ "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" - } - } + "proration": null } ] }, @@ -249,13 +243,7 @@ "tax": "887", "total": "10887" }, - "proration": { - "rate": "1", - "billing_period": { - "starts_at": "2024-03-08T11:17:08.807055Z", - "ends_at": "2024-04-08T11:17:08.807055Z" - } - } + "proration": null } ] }, @@ -324,7 +312,8 @@ "discount": "0", "tax": "1766", "total": "21666" - } + }, + "proration": null } ] }, @@ -378,6 +367,19 @@ "import_meta": null, "created_at": "2023-02-23T13:55:22.538367Z", "updated_at": "2023-11-09T14:07:16.051528Z" + }, + "product": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "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" } }, { @@ -415,6 +417,19 @@ "import_meta": null, "created_at": "2023-06-01T13:31:12.625056Z", "updated_at": "2023-08-30T10:34:33.862679Z" + }, + "product": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "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" } } ], 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 aec9467..0ae9080 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 @@ -128,13 +128,7 @@ "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" - } - } + "proration": null } ] }, @@ -249,13 +243,7 @@ "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" - } - } + "proration": null } ] }, @@ -372,13 +360,7 @@ "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" - } - } + "proration": null } ] }, diff --git a/tests/Functional/Resources/Transactions/TransactionsClientTest.php b/tests/Functional/Resources/Transactions/TransactionsClientTest.php index d797fd4..99b0810 100644 --- a/tests/Functional/Resources/Transactions/TransactionsClientTest.php +++ b/tests/Functional/Resources/Transactions/TransactionsClientTest.php @@ -648,6 +648,30 @@ public function get_has_payments_with_and_without_payment_method_id(): void self::assertNull($paymentWithoutPaymentMethodId->paymentMethodId); } + /** + * @test + */ + public function get_returns_nullable_proration(): void + { + $this->mockClient->addResponse(new Response(200, body: self::readRawJsonFixture('response/full_entity'))); + $transaction = $this->client->transactions->get('txn_01hen7bxc1p8ep4yk7n5jbzk9r'); + + $lineItemProration = $transaction->details->lineItems[0]->proration; + self::assertNotNull($lineItemProration); + self::assertEquals('1', $lineItemProration->rate); + self::assertEquals( + '2024-02-08T11:02:03+00:00', + $lineItemProration->billingPeriod->startsAt->format(DATE_RFC3339), + ); + self::assertEquals( + '2024-03-08T11:02:03+00:00', + $lineItemProration->billingPeriod->endsAt->format(DATE_RFC3339), + ); + + $nullLineItemProration = $transaction->details->lineItems[1]->proration; + self::assertNull($nullLineItemProration); + } + /** * @test * @@ -899,6 +923,36 @@ public function it_has_prices_with_and_without_id_on_preview(): void self::assertNull($previewProduct->id); } + /** + * @test + */ + public function preview_returns_nullable_proration(): 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), + ], + ), + ); + + $lineItemProration = $preview->details->lineItems[0]->proration; + self::assertNotNull($lineItemProration); + self::assertEquals('1', $lineItemProration->rate); + self::assertEquals( + '2024-02-08T11:02:03+00:00', + $lineItemProration->billingPeriod->startsAt->format(DATE_RFC3339), + ); + self::assertEquals( + '2024-03-08T11:02:03+00:00', + $lineItemProration->billingPeriod->endsAt->format(DATE_RFC3339), + ); + + $nullLineItemProration = $preview->details->lineItems[1]->proration; + self::assertNull($nullLineItemProration); + } + /** * @test * diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json b/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json index ebe6394..2db3ff4 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/full_entity.json @@ -155,7 +155,55 @@ "tax": "486", "discount": "10", "total": "2915" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-02-08T11:02:03.946454Z", + "ends_at": "2024-03-08T11:02:03.946454Z" + } } + }, + { + "id": "txnitm_01hen7bxecbsbdd65s8qhyv7jw", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 1, + "totals": { + "subtotal": "2439", + "tax": "486", + "discount": "10", + "total": "2915" + }, + "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", + "custom_data": { + "features": { + "crm": true, + "data_retention": false, + "reports": true + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of advanced reporting and a CRM that's right where you're chatting." + }, + "status": "active", + "created_at": "2023-08-16T14:38:08.3Z", + "updated_at": "2023-08-16T14:38:08.3Z" + }, + "tax_rate": "0.2", + "unit_totals": { + "subtotal": "2439", + "tax": "486", + "discount": "10", + "total": "2915" + }, + "proration": null } ] }, diff --git a/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json b/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json index e0a6d67..9b98256 100644 --- a/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json +++ b/tests/Functional/Resources/Transactions/_fixtures/response/preview_entity.json @@ -164,6 +164,13 @@ "tax": "0", "discount": "3000", "total": "27000" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-02-08T11:02:03.946454Z", + "ends_at": "2024-03-08T11:02:03.946454Z" + } } }, { @@ -192,7 +199,8 @@ "tax": "0", "discount": "1990", "total": "17910" - } + }, + "proration": null } ] },