diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8c135..1de8048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-php-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools. +## [1.7.2] - 2024-12-17 + +### Fixed + +- Adjustment items can be omitted for when adjustment type is full + ## [1.7.1] - 2024-12-13 ### Fixed diff --git a/examples/adjustments.php b/examples/adjustments.php new file mode 100644 index 0000000..4f4a934 --- /dev/null +++ b/examples/adjustments.php @@ -0,0 +1,69 @@ +adjustments->create( + CreateAdjustment::partial( + Action::Refund(), + [ + new AdjustmentItem( + $transactionItemId, + AdjustmentType::Partial(), + '100', + ), + ], + 'error', + $transactionId, + ), + ); +} catch (ApiError|MalformedResponse $e) { + var_dump($e); + exit; +} + +echo sprintf("Partial Adjustment ID: %s\n", $partialAdjustment->id); + +// ┌─── +// │ Create Full Adjustment │ +// └────────────────────────┘ +try { + $fullAdjustment = $paddle->adjustments->create( + CreateAdjustment::full( + Action::Refund(), + 'error', + $fullAdjustmentTransactionId, + ), + ); +} catch (ApiError|MalformedResponse $e) { + var_dump($e); + exit; +} + +echo sprintf("Full Adjustment ID: %s\n", $fullAdjustment->id); diff --git a/src/Client.php b/src/Client.php index b8836ca..4a49986 100644 --- a/src/Client.php +++ b/src/Client.php @@ -60,7 +60,7 @@ class Client { - private const SDK_VERSION = '1.7.1'; + private const SDK_VERSION = '1.7.2'; public readonly LoggerInterface $logger; public readonly Options $options; diff --git a/src/Resources/Adjustments/Operations/CreateAdjustment.php b/src/Resources/Adjustments/Operations/CreateAdjustment.php index 9b0ef41..f969ae9 100644 --- a/src/Resources/Adjustments/Operations/CreateAdjustment.php +++ b/src/Resources/Adjustments/Operations/CreateAdjustment.php @@ -22,21 +22,37 @@ class CreateAdjustment implements \JsonSerializable */ public function __construct( public readonly Action $action, - public readonly array|Undefined $items, + public readonly array|Undefined|null $items, public readonly string $reason, public readonly string $transactionId, public readonly AdjustmentType|Undefined $type = new Undefined(), ) { - if ($this->type === AdjustmentType::Partial() && ($this->items instanceof Undefined || empty($this->items))) { + $typeIsFull = AdjustmentType::Full()->equals($this->type); + + if (! $typeIsFull && ($this->items instanceof Undefined || empty($this->items))) { throw InvalidArgumentException::arrayIsEmpty('items'); } + + if ($typeIsFull && is_array($this->items)) { + throw new InvalidArgumentException('items are not allowed when the adjustment type is full'); + } + } + + public static function full(Action $action, string $reason, string $transactionId): self + { + return new self($action, new Undefined(), $reason, $transactionId, AdjustmentType::Full()); + } + + public static function partial(Action $action, array $items, string $reason, string $transactionId): self + { + return new self($action, $items, $reason, $transactionId, AdjustmentType::Partial()); } public function jsonSerialize(): array { - $items = []; + if (is_array($this->items)) { + $items = []; - if (! $this->items instanceof Undefined) { foreach ($this->items as $item) { $items[] = [ 'item_id' => $item->itemId, @@ -44,6 +60,8 @@ public function jsonSerialize(): array 'amount' => $item->amount, ]; } + } else { + $items = $this->items; } return $this->filterUndefined([ diff --git a/tests/Functional/Resources/Adjustments/AdjustmentsClientTest.php b/tests/Functional/Resources/Adjustments/AdjustmentsClientTest.php index e154311..dfe5245 100644 --- a/tests/Functional/Resources/Adjustments/AdjustmentsClientTest.php +++ b/tests/Functional/Resources/Adjustments/AdjustmentsClientTest.php @@ -100,6 +100,43 @@ public static function createOperationsProvider(): \Generator new Response(200, body: self::readRawJsonFixture('response/full_entity')), self::readRawJsonFixture('request/create_full'), ]; + + yield 'Partial type with items' => [ + CreateAdjustment::partial( + Action::Refund(), + [new AdjustmentItem( + 'txnitm_01h8bxryv3065dyh6103p3yg28', + AdjustmentType::Partial(), + '100', + )], + 'error', + 'txn_01h8bxpvx398a7zbawb77y0kp5', + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/create_type_partial_with_items'), + ]; + + yield 'Full type with no items' => [ + CreateAdjustment::full( + Action::Refund(), + 'error', + 'txn_01h8bxpvx398a7zbawb77y0kp5', + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/create_type_full_with_no_items'), + ]; + + yield 'Full type with null items' => [ + new CreateAdjustment( + Action::Refund(), + null, + 'error', + 'txn_01h8bxpvx398a7zbawb77y0kp5', + \Paddle\SDK\Entities\Adjustment\AdjustmentType::Full(), + ), + new Response(200, body: self::readRawJsonFixture('response/minimal_entity')), + self::readRawJsonFixture('request/create_type_full_with_null_items'), + ]; } /** diff --git a/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_full_with_no_items.json b/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_full_with_no_items.json new file mode 100644 index 0000000..2c6d204 --- /dev/null +++ b/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_full_with_no_items.json @@ -0,0 +1,6 @@ +{ + "action": "refund", + "type": "full", + "reason": "error", + "transaction_id": "txn_01h8bxpvx398a7zbawb77y0kp5" +} diff --git a/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_full_with_null_items.json b/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_full_with_null_items.json new file mode 100644 index 0000000..09698c8 --- /dev/null +++ b/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_full_with_null_items.json @@ -0,0 +1,7 @@ +{ + "action": "refund", + "type": "full", + "items": null, + "reason": "error", + "transaction_id": "txn_01h8bxpvx398a7zbawb77y0kp5" +} diff --git a/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_partial_with_items.json b/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_partial_with_items.json new file mode 100644 index 0000000..7800085 --- /dev/null +++ b/tests/Functional/Resources/Adjustments/_fixtures/request/create_type_partial_with_items.json @@ -0,0 +1,13 @@ +{ + "action": "refund", + "type": "partial", + "items": [ + { + "item_id": "txnitm_01h8bxryv3065dyh6103p3yg28", + "type": "partial", + "amount": "100" + } + ], + "reason": "error", + "transaction_id": "txn_01h8bxpvx398a7zbawb77y0kp5" +} diff --git a/tests/Unit/Resources/Adjustments/Operations/CreateAdjustmentTest.php b/tests/Unit/Resources/Adjustments/Operations/CreateAdjustmentTest.php new file mode 100644 index 0000000..1377f12 --- /dev/null +++ b/tests/Unit/Resources/Adjustments/Operations/CreateAdjustmentTest.php @@ -0,0 +1,58 @@ + [ + [], + AdjustmentType::Partial(), + 'items cannot be empty', + ]; + yield 'Undefined' => [ + new Undefined(), + AdjustmentType::Partial(), + 'items cannot be empty', + ]; + yield 'Null' => [ + null, + AdjustmentType::Partial(), + 'items cannot be empty', + ]; + yield 'Items for full type' => [ + [], + AdjustmentType::Full(), + 'items are not allowed when the adjustment type is full', + ]; + } +}