From 5167875b7849c96b3d6426110e53cee892ee491c Mon Sep 17 00:00:00 2001 From: joeydehaas Date: Fri, 6 Sep 2024 09:58:53 +0200 Subject: [PATCH 1/5] INTEG-254: checkout update --- .../Mock/CheckoutUpdateMockClient.php | 27 ++++++++ Tests/Feature/Checkout/UpdateCheckoutTest.php | 61 +++++++++++++++++++ src/Entity/Checkout.php | 27 +------- src/Entity/CheckoutInternal.php | 20 ++++++ src/Service/CheckoutService.php | 17 ++++++ 5 files changed, 127 insertions(+), 25 deletions(-) create mode 100644 Tests/Feature/Checkout/Mock/CheckoutUpdateMockClient.php create mode 100644 Tests/Feature/Checkout/UpdateCheckoutTest.php diff --git a/Tests/Feature/Checkout/Mock/CheckoutUpdateMockClient.php b/Tests/Feature/Checkout/Mock/CheckoutUpdateMockClient.php new file mode 100644 index 0000000..b3adce0 --- /dev/null +++ b/Tests/Feature/Checkout/Mock/CheckoutUpdateMockClient.php @@ -0,0 +1,27 @@ +responseBody = Arr::mergeDistinct($this->responseBody, ['data' => $data]); + $this->path = $path; + $this->requestBody = $data; + + return new Response(Response::HTTP_OK, $this->responseBody); + } + + public function path(): string + { + return $this->path; + } +} diff --git a/Tests/Feature/Checkout/UpdateCheckoutTest.php b/Tests/Feature/Checkout/UpdateCheckoutTest.php new file mode 100644 index 0000000..e18d1e4 --- /dev/null +++ b/Tests/Feature/Checkout/UpdateCheckoutTest.php @@ -0,0 +1,61 @@ +update(999, function (Checkout $checkout) { + }); + } catch (UnauthenticatedException $exception) { + } + + static::assertEquals('Unable to connect with Plug&Pay. Request is unauthenticated.', $exception->getMessage()); + } + + /** @test */ + public function it_should_update_basic_checkout(): void + { + $client = new CheckoutUpdateMockClient(); + $service = new CheckoutService($client); + + $checkout = $service->update(1, function (Checkout $checkout) { + $checkout->setIsActive(false); + }); + + static::assertFalse($checkout->isActive()); + static::assertEquals('/v2/checkouts/1', $client->path()); + } + + /** @test */ + public function it_should_update_product_id(): void + { + $client = (new CheckoutUpdateMockClient())->productPricing(); + $service = new CheckoutService($client); + + $checkout = $service->update(1, function (Checkout $checkout) { + $checkout->setProductId(123); + }); + + static::assertEquals(123, $checkout->product()->id()); + } +} diff --git a/src/Entity/Checkout.php b/src/Entity/Checkout.php index 7aaf7ea..61f0198 100644 --- a/src/Entity/Checkout.php +++ b/src/Entity/Checkout.php @@ -111,32 +111,9 @@ public function product(): Product return $this->product; } - public function setProduct(Product $product): self + public function setProductId(int $id): self { - $this->product = $product; - - return $this; - } - - /** - * @throws RelationNotLoadedException - */ - public function productPricing(): ProductPricing - { - if (!isset($this->productPricing)) { - if ($this->allowEmptyRelations) { - $this->productPricing = new ProductPricing($this->allowEmptyRelations); - } else { - throw new RelationNotLoadedException('pricing'); - } - } - - return $this->productPricing; - } - - public function setProductPricing(ProductPricing $productPricing): self - { - $this->productPricing = $productPricing; + $this->product = (new ProductInternal())->setId($id); return $this; } diff --git a/src/Entity/CheckoutInternal.php b/src/Entity/CheckoutInternal.php index dcdbdba..d9ac045 100644 --- a/src/Entity/CheckoutInternal.php +++ b/src/Entity/CheckoutInternal.php @@ -18,6 +18,26 @@ public function setId(int $id): self return $this; } + /** + * @internal + */ + public function setProduct(Product $product): self + { + $this->product = $product; + + return $this; + } + + /** + * @internal + */ + public function setProductPricing(ProductPricing $productPricing): self + { + $this->productPricing = $productPricing; + + return $this; + } + /** * @internal */ diff --git a/src/Service/CheckoutService.php b/src/Service/CheckoutService.php index b69a955..0e98142 100644 --- a/src/Service/CheckoutService.php +++ b/src/Service/CheckoutService.php @@ -6,9 +6,11 @@ use PlugAndPay\Sdk\Contract\ClientInterface; use PlugAndPay\Sdk\Director\BodyTo\BodyToCheckout; +use PlugAndPay\Sdk\Director\ToBody\CheckoutToBody; use PlugAndPay\Sdk\Entity\Checkout; use PlugAndPay\Sdk\Enum\CheckoutIncludes; use PlugAndPay\Sdk\Exception\DecodeResponseException; +use PlugAndPay\Sdk\Exception\RelationNotLoadedException; use PlugAndPay\Sdk\Support\Parameters; class CheckoutService @@ -37,4 +39,19 @@ public function find(int $id): Checkout return BodyToCheckout::build($response->body()['data']); } + + /** + * @throws DecodeResponseException + * @throws RelationNotLoadedException + */ + public function update(int $checkoutId, callable $update): Checkout + { + $order = new Checkout(true); + $update($order); + $body = CheckoutToBody::build($order); + $query = Parameters::toString(['include' => $this->includes]); + $response = $this->client->patch("/v2/checkouts/$checkoutId$query", $body); + + return BodyToCheckout::build($response->body()['data']); + } } From 1edf9d59f0aa5262c1d92597c083817399301eac Mon Sep 17 00:00:00 2001 From: joeydehaas Date: Fri, 6 Sep 2024 10:17:18 +0200 Subject: [PATCH 2/5] INTEG-253: checkout destroy --- Tests/Feature/Checkout/DeleteCheckoutTest.php | 60 +++++++++++++++++++ .../Mock/CheckoutDestroyMockClient.php | 38 ++++++++++++ src/Service/CheckoutService.php | 5 ++ 3 files changed, 103 insertions(+) create mode 100644 Tests/Feature/Checkout/DeleteCheckoutTest.php create mode 100644 Tests/Feature/Checkout/Mock/CheckoutDestroyMockClient.php diff --git a/Tests/Feature/Checkout/DeleteCheckoutTest.php b/Tests/Feature/Checkout/DeleteCheckoutTest.php new file mode 100644 index 0000000..9382b69 --- /dev/null +++ b/Tests/Feature/Checkout/DeleteCheckoutTest.php @@ -0,0 +1,60 @@ +delete(1); + } catch (NotFoundException $exception) { + } + + static::assertInstanceOf(NotFoundException::class, $exception); + } + + /** @test */ + public function it_should_throw_unauthorised_exception(): void + { + $client = new CheckoutDestroyMockClient(Response::HTTP_UNAUTHORIZED, []); + $service = new CheckoutService($client); + $exception = null; + + try { + $service->delete(1); + } catch (UnauthenticatedException $exception) { + } + + static::assertInstanceOf(UnauthenticatedException::class, $exception); + } + + /** @test */ + public function it_should_delete_checkout(): void + { + $client = new CheckoutDestroyMockClient(Response::HTTP_NO_CONTENT, []); + $service = new CheckoutService($client); + + $service->delete(1); + + static::assertEquals('/v2/checkouts/1', $client->path()); + } +} diff --git a/Tests/Feature/Checkout/Mock/CheckoutDestroyMockClient.php b/Tests/Feature/Checkout/Mock/CheckoutDestroyMockClient.php new file mode 100644 index 0000000..8cdf5b6 --- /dev/null +++ b/Tests/Feature/Checkout/Mock/CheckoutDestroyMockClient.php @@ -0,0 +1,38 @@ +path = $path; + $exception = ExceptionFactory::create($this->status); + if ($exception) { + throw $exception; + } + + return new Response(Response::HTTP_NO_CONTENT); + } + + public function path(): string + { + return $this->path; + } +} diff --git a/src/Service/CheckoutService.php b/src/Service/CheckoutService.php index b69a955..757c981 100644 --- a/src/Service/CheckoutService.php +++ b/src/Service/CheckoutService.php @@ -37,4 +37,9 @@ public function find(int $id): Checkout return BodyToCheckout::build($response->body()['data']); } + + public function delete(int $checkoutId): void + { + $this->client->delete("/v2/checkouts/$checkoutId"); + } } From 1144e7e4ecc71b20a98d0b9fbe7e737d2c26e898 Mon Sep 17 00:00:00 2001 From: JoeyDeHaas Date: Fri, 6 Sep 2024 08:18:13 +0000 Subject: [PATCH 3/5] Apply php-cs-fixer changes --- Tests/Feature/Checkout/DeleteCheckoutTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/Feature/Checkout/DeleteCheckoutTest.php b/Tests/Feature/Checkout/DeleteCheckoutTest.php index 9382b69..5213791 100644 --- a/Tests/Feature/Checkout/DeleteCheckoutTest.php +++ b/Tests/Feature/Checkout/DeleteCheckoutTest.php @@ -11,9 +11,7 @@ use PlugAndPay\Sdk\Exception\NotFoundException; use PlugAndPay\Sdk\Exception\UnauthenticatedException; use PlugAndPay\Sdk\Service\CheckoutService; -use PlugAndPay\Sdk\Service\OrderService; use PlugAndPay\Sdk\Tests\Feature\Checkout\Mock\CheckoutDestroyMockClient; -use PlugAndPay\Sdk\Tests\Feature\Order\Mock\OrderDestroyMockClient; class DeleteCheckoutTest extends TestCase { From a9d60c99ee71ca11e684b1934a65e8635835713b Mon Sep 17 00:00:00 2001 From: joeydehaas Date: Fri, 6 Sep 2024 10:21:00 +0200 Subject: [PATCH 4/5] INTEG-254: allow product pricing in include --- src/Entity/Checkout.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Entity/Checkout.php b/src/Entity/Checkout.php index 61f0198..50613f3 100644 --- a/src/Entity/Checkout.php +++ b/src/Entity/Checkout.php @@ -118,6 +118,22 @@ public function setProductId(int $id): self return $this; } + /** + * @throws RelationNotLoadedException + */ + public function productPricing(): ProductPricing + { + if (!isset($this->productPricing)) { + if ($this->allowEmptyRelations) { + $this->productPricing = new ProductPricing($this->allowEmptyRelations); + } else { + throw new RelationNotLoadedException('pricing'); + } + } + + return $this->productPricing; + } + public function returnUrl(): ?string { return $this->returnUrl; From efa6abae0bf36d0ef0e87c8e2bc07ef2fd2343d5 Mon Sep 17 00:00:00 2001 From: joeydehaas Date: Fri, 6 Sep 2024 10:45:07 +0200 Subject: [PATCH 5/5] INTEG-256: checkout store --- .../Checkout/Mock/CheckoutStoreMockClient.php | 30 ++++ Tests/Feature/Checkout/StoreCheckoutTest.php | 142 ++++++++++++++++++ src/Service/CheckoutService.php | 10 ++ 3 files changed, 182 insertions(+) create mode 100644 Tests/Feature/Checkout/Mock/CheckoutStoreMockClient.php create mode 100644 Tests/Feature/Checkout/StoreCheckoutTest.php diff --git a/Tests/Feature/Checkout/Mock/CheckoutStoreMockClient.php b/Tests/Feature/Checkout/Mock/CheckoutStoreMockClient.php new file mode 100644 index 0000000..961b6f7 --- /dev/null +++ b/Tests/Feature/Checkout/Mock/CheckoutStoreMockClient.php @@ -0,0 +1,30 @@ +path; + } + + public function post(string $path, array $body): Response + { + $this->path = $path; + $this->requestBody = $body; + + return new Response(Response::HTTP_CREATED, $this->responseBody); + } + + public function requestBody(): array + { + return $this->requestBody; + } +} diff --git a/Tests/Feature/Checkout/StoreCheckoutTest.php b/Tests/Feature/Checkout/StoreCheckoutTest.php new file mode 100644 index 0000000..e36c02f --- /dev/null +++ b/Tests/Feature/Checkout/StoreCheckoutTest.php @@ -0,0 +1,142 @@ +isset('bad_function'); + } catch (BadFunctionCallException $exception) { + } + + static::assertInstanceOf(BadFunctionCallException::class, $exception); + } + + /** @test */ + public function it_should_convert_basic_checkout_to_body(): void + { + $body = CheckoutToBody::build($this->generateCheckout()); + + static::assertEquals([ + 'is_active' => true, + 'is_expired' => false, + 'name' => 'the-name', + 'preview_url' => 'the-url', + 'primary_color' => 'the-primary-color', + 'return_url' => 'the-return-url', + 'secondary_color' => 'the-secondary-color', + 'slug' => 'the-slug', + 'url' => 'the-url', + 'product' => [ + 'id' => 1, + ], + ], $body); + } + + /** @test */ + public function it_should_store_basic_checkout(): void + { + $client = new CheckoutStoreMockClient(); + $service = new CheckoutService($client); + + $checkout = $this->generateCheckout(); + $checkout = $service->create($checkout); + + static::assertEquals('/v2/checkouts', $client->path()); + static::assertEquals(1, $checkout->id()); + } + + /** @test */ + public function create_order_with_validation_error(): void + { + $client = new ClientMock( + Response::HTTP_UNPROCESSABLE_ENTITY, + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'name' => [ + 'Naam is verplicht.', + ], + ], + ], + ); + $service = new CheckoutService($client); + $exception = null; + + try { + $service->find(1); + } catch (ValidationException $exception) { + } + + static::assertEquals('Naam is verplicht.', $exception->getMessage()); + static::assertEquals('Naam is verplicht.', $exception->errors()[0]->message()); + static::assertEquals('name', $exception->errors()[0]->field()); + } + + /** @test */ + public function create_order_with_multiple_validation_errors(): void + { + $client = new ClientMock( + Response::HTTP_UNPROCESSABLE_ENTITY, + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'name' => [ + 'First error.', + 'Second error.', + ], + 'productId' => [ + 'Last error.', + ], + ], + ], + ); + $service = new CheckoutService($client); + $exception = null; + + try { + $service->find(1); + } catch (ValidationException $exception) { + } + + static::assertEquals('First error. Second error. Last error.', $exception->getMessage()); + static::assertEquals('First error.', $exception->errors()[0]->message()); + static::assertEquals('name', $exception->errors()[0]->field()); + } + + private function generateCheckout(): Checkout + { + return (new Checkout()) + ->setIsActive(true) + ->setIsExpired(false) + ->setName('the-name') + ->setPreviewUrl('the-url') + ->setPrimaryColor('the-primary-color') + ->setProductId(1) + ->setReturnUrl('the-return-url') + ->setSecondaryColor('the-secondary-color') + ->setSlug('the-slug') + ->setUrl('the-url'); + } +} diff --git a/src/Service/CheckoutService.php b/src/Service/CheckoutService.php index 0e98142..be490f9 100644 --- a/src/Service/CheckoutService.php +++ b/src/Service/CheckoutService.php @@ -40,6 +40,16 @@ public function find(int $id): Checkout return BodyToCheckout::build($response->body()['data']); } + /** @throws DecodeResponseException */ + public function create(Checkout $checkout): Checkout + { + $body = CheckoutToBody::build($checkout); + $query = Parameters::toString(['include' => $this->includes]); + $response = $this->client->post("/v2/checkouts$query", $body); + + return BodyToCheckout::build($response->body()['data']); + } + /** * @throws DecodeResponseException * @throws RelationNotLoadedException