diff --git a/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutController.php b/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutController.php index f95040de..106b1905 100644 --- a/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutController.php +++ b/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutController.php @@ -64,15 +64,18 @@ public function __construct( */ public function isAvailable(ExpressCheckoutAvailabilityRequest $request): ExpressCheckoutAvailabilityResponse { + $available = $this->expressCheckoutService->isExpressCheckoutAvailable( + $request->getPage(), + $request->getCountry(), + $request->getCurrency(), + $request->getIpAddress(), + $request->getProductIds(), + $request->getCategoryIds() + ); + return new ExpressCheckoutAvailabilityResponse( - $this->expressCheckoutService->isExpressCheckoutAvailable( - $request->getPage(), - $request->getCountry(), - $request->getCurrency(), - $request->getIpAddress(), - $request->getProductIds(), - $request->getCategoryIds() - ) + $available, + $available ? $this->resolveButtonStyle() : null ); } @@ -102,8 +105,13 @@ public function isAvailableForGuest( ); $countries = $available ? $this->countryConfigurationService->getCountryCodes() : []; + $hasCountries = !empty($countries); - return new GuestExpressCheckoutAvailabilityResponse(!empty($countries), $countries); + return new GuestExpressCheckoutAvailabilityResponse( + $hasCountries, + $countries, + $hasCountries ? $this->resolveButtonStyle() : null + ); } /** @@ -135,4 +143,14 @@ public function solicit(ExpressCheckoutSolicitRequest $request): IdentificationF return new IdentificationFormResponse($form); } + + /** + * @return string|null + */ + private function resolveButtonStyle(): ?string + { + $settings = $this->expressCheckoutService->getExpressCheckoutSettings(); + + return $settings ? $settings->getButtonStyle() : null; + } } diff --git a/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/ExpressCheckoutAvailabilityResponse.php b/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/ExpressCheckoutAvailabilityResponse.php index 963f269b..c679623d 100644 --- a/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/ExpressCheckoutAvailabilityResponse.php +++ b/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/ExpressCheckoutAvailabilityResponse.php @@ -16,12 +16,19 @@ class ExpressCheckoutAvailabilityResponse extends Response */ protected $available; + /** + * @var string|null + */ + protected $buttonStyle; + /** * @param bool $available + * @param string|null $buttonStyle */ - public function __construct(bool $available) + public function __construct(bool $available, ?string $buttonStyle = null) { $this->available = $available; + $this->buttonStyle = $buttonStyle; } /** @@ -29,6 +36,9 @@ public function __construct(bool $available) */ public function toArray(): array { - return ['available' => $this->available]; + return [ + 'available' => $this->available, + 'buttonStyle' => $this->buttonStyle, + ]; } } diff --git a/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/GuestExpressCheckoutAvailabilityResponse.php b/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/GuestExpressCheckoutAvailabilityResponse.php index d546a128..f157f857 100644 --- a/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/GuestExpressCheckoutAvailabilityResponse.php +++ b/src/BusinessLogic/CheckoutAPI/ExpressCheckout/Responses/GuestExpressCheckoutAvailabilityResponse.php @@ -21,14 +21,24 @@ class GuestExpressCheckoutAvailabilityResponse extends Response */ protected $availableCountries; + /** + * @var string|null + */ + protected $buttonStyle; + /** * @param bool $available * @param string[] $availableCountries ISO country codes for which Express Checkout is available. + * @param string|null $buttonStyle */ - public function __construct(bool $available, array $availableCountries) - { + public function __construct( + bool $available, + array $availableCountries, + ?string $buttonStyle = null + ) { $this->available = $available; $this->availableCountries = $availableCountries; + $this->buttonStyle = $buttonStyle; } /** @@ -39,6 +49,7 @@ public function toArray(): array return [ 'available' => $this->available, 'availableCountries' => $this->availableCountries, + 'buttonStyle' => $this->buttonStyle, ]; } } diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/ExpressCheckout/SaveExpressCheckoutSettingsHandler.php b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/ExpressCheckout/SaveExpressCheckoutSettingsHandler.php index 3d018b55..4748aa4b 100644 --- a/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/ExpressCheckout/SaveExpressCheckoutSettingsHandler.php +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Handlers/ExpressCheckout/SaveExpressCheckoutSettingsHandler.php @@ -7,6 +7,7 @@ use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Requests\ExpressCheckout\SaveExpressCheckoutSettingsRequest; use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Responses\ExpressCheckout\SaveExpressCheckoutSettingsResponse; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\DuplicatedExpressCheckoutPageException; +use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutButtonStyleException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutPageConfigException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutPageException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Services\ExpressCheckoutService; @@ -39,6 +40,7 @@ public function __construct(ExpressCheckoutService $expressCheckoutService) * @throws InvalidExpressCheckoutPageException * @throws DuplicatedExpressCheckoutPageException * @throws InvalidExpressCheckoutPageConfigException + * @throws InvalidExpressCheckoutButtonStyleException */ public function handle(array $payload): Response { diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Requests/ExpressCheckout/SaveExpressCheckoutSettingsRequest.php b/src/BusinessLogic/ConfigurationWebhookAPI/Requests/ExpressCheckout/SaveExpressCheckoutSettingsRequest.php index 932abd42..cc0b101f 100644 --- a/src/BusinessLogic/ConfigurationWebhookAPI/Requests/ExpressCheckout/SaveExpressCheckoutSettingsRequest.php +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Requests/ExpressCheckout/SaveExpressCheckoutSettingsRequest.php @@ -4,6 +4,7 @@ use SeQura\Core\BusinessLogic\ConfigurationWebhookAPI\Requests\ConfigurationWebhookRequest; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\DuplicatedExpressCheckoutPageException; +use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutButtonStyleException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutPageConfigException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutPageException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Models\ExpressCheckoutPageConfig; @@ -21,12 +22,19 @@ class SaveExpressCheckoutSettingsRequest extends ConfigurationWebhookRequest */ protected $expressCheckoutConfigs; + /** + * @var string|null + */ + protected $buttonStyle; + /** * @param ExpressCheckoutPageConfig[] $expressCheckoutConfigs + * @param string|null $buttonStyle */ - public function __construct(array $expressCheckoutConfigs) + public function __construct(array $expressCheckoutConfigs, ?string $buttonStyle = null) { $this->expressCheckoutConfigs = $expressCheckoutConfigs; + $this->buttonStyle = $buttonStyle; } /** @@ -35,6 +43,7 @@ public function __construct(array $expressCheckoutConfigs) * @return self * * @throws InvalidExpressCheckoutPageException + * @throws InvalidExpressCheckoutButtonStyleException */ public static function fromPayload(array $payload): object { @@ -47,7 +56,13 @@ public static function fromPayload(array $payload): object } } - return new self($configs); + $buttonStyle = $payload['buttonStyle'] ?? null; + + if ($buttonStyle !== null && !\is_string($buttonStyle)) { + throw new InvalidExpressCheckoutButtonStyleException(); + } + + return new self($configs, $buttonStyle); } /** @@ -55,9 +70,10 @@ public static function fromPayload(array $payload): object * * @throws DuplicatedExpressCheckoutPageException * @throws InvalidExpressCheckoutPageConfigException + * @throws InvalidExpressCheckoutButtonStyleException */ public function transformToDomainModel(): ExpressCheckoutSettings { - return new ExpressCheckoutSettings($this->expressCheckoutConfigs); + return new ExpressCheckoutSettings($this->expressCheckoutConfigs, $this->buttonStyle); } } diff --git a/src/BusinessLogic/ConfigurationWebhookAPI/Responses/ExpressCheckout/GetExpressCheckoutSettingsResponse.php b/src/BusinessLogic/ConfigurationWebhookAPI/Responses/ExpressCheckout/GetExpressCheckoutSettingsResponse.php index c5f87ff5..c9363a18 100644 --- a/src/BusinessLogic/ConfigurationWebhookAPI/Responses/ExpressCheckout/GetExpressCheckoutSettingsResponse.php +++ b/src/BusinessLogic/ConfigurationWebhookAPI/Responses/ExpressCheckout/GetExpressCheckoutSettingsResponse.php @@ -42,6 +42,9 @@ public function toArray(): array $configs = $this->expressCheckoutSettings ? $this->expressCheckoutSettings->getExpressCheckoutConfigs() : []; + $buttonStyle = $this->expressCheckoutSettings + ? $this->expressCheckoutSettings->getButtonStyle() + : null; return [ 'availablePages' => array_map(static function (ExpressCheckoutPage $page) { @@ -50,6 +53,7 @@ public function toArray(): array 'expressCheckoutConfigs' => array_map(static function (ExpressCheckoutPageConfig $config) { return $config->toArray(); }, $configs), + 'buttonStyle' => $buttonStyle, ]; } } diff --git a/src/BusinessLogic/DataAccess/ExpressCheckout/Entities/ExpressCheckoutSettings.php b/src/BusinessLogic/DataAccess/ExpressCheckout/Entities/ExpressCheckoutSettings.php index 8dc041ac..6efd63c2 100644 --- a/src/BusinessLogic/DataAccess/ExpressCheckout/Entities/ExpressCheckoutSettings.php +++ b/src/BusinessLogic/DataAccess/ExpressCheckout/Entities/ExpressCheckoutSettings.php @@ -3,6 +3,7 @@ namespace SeQura\Core\BusinessLogic\DataAccess\ExpressCheckout\Entities; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\DuplicatedExpressCheckoutPageException; +use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutButtonStyleException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutPageConfigException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Exceptions\InvalidExpressCheckoutPageException; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Models\ExpressCheckoutPageConfig; @@ -53,7 +54,20 @@ public function inflate(array $data): void } } - $this->expressCheckoutSettings = new DomainExpressCheckoutSettings($configs); + $buttonStyle = static::getDataValue($expressCheckoutSettings, 'buttonStyle', null); + if (!\is_string($buttonStyle)) { + $buttonStyle = null; + } + + // A style is rejected when it is written, so a stored one that no longer + // validates means the row was corrupted or hand-edited. Dropping just the + // style keeps the page configs readable: the button falls back to its + // default look instead of every read of this row failing. + try { + $this->expressCheckoutSettings = new DomainExpressCheckoutSettings($configs, $buttonStyle); + } catch (InvalidExpressCheckoutButtonStyleException $exception) { + $this->expressCheckoutSettings = new DomainExpressCheckoutSettings($configs); + } } /** @@ -63,11 +77,7 @@ public function toArray(): array { $data = parent::toArray(); $data['storeId'] = $this->storeId; - $data['expressCheckoutSettings'] = [ - 'expressCheckoutConfigs' => array_map(static function (ExpressCheckoutPageConfig $config) { - return $config->toArray(); - }, $this->expressCheckoutSettings->getExpressCheckoutConfigs()), - ]; + $data['expressCheckoutSettings'] = $this->expressCheckoutSettings->toArray(); return $data; } diff --git a/src/BusinessLogic/Domain/ExpressCheckout/Exceptions/InvalidExpressCheckoutButtonStyleException.php b/src/BusinessLogic/Domain/ExpressCheckout/Exceptions/InvalidExpressCheckoutButtonStyleException.php new file mode 100644 index 00000000..2c8e4ecc --- /dev/null +++ b/src/BusinessLogic/Domain/ExpressCheckout/Exceptions/InvalidExpressCheckoutButtonStyleException.php @@ -0,0 +1,31 @@ +validateConfigs($expressCheckoutConfigs); $this->expressCheckoutConfigs = array_values($expressCheckoutConfigs); + $this->buttonStyle = $this->normalizeButtonStyle($buttonStyle); } /** @@ -39,6 +59,14 @@ public function getExpressCheckoutConfigs(): array return $this->expressCheckoutConfigs; } + /** + * @return string|null + */ + public function getButtonStyle(): ?string + { + return $this->buttonStyle; + } + /** * @param string $page Page identifier string (see ExpressCheckoutPage factories). * @@ -64,6 +92,7 @@ public function toArray(): array 'expressCheckoutConfigs' => array_map(static function (ExpressCheckoutPageConfig $config) { return $config->toArray(); }, $this->expressCheckoutConfigs), + 'buttonStyle' => $this->buttonStyle, ]; } @@ -95,4 +124,28 @@ private function validateConfigs(array $expressCheckoutConfigs): void $seenPages[] = $page; } } + + /** + * Treats an empty style as unset and asserts that anything else is a JSON + * object within the size limit. A decoded scalar or list is rejected: the + * button page reads named properties off an object. + * + * @param string|null $buttonStyle + * + * @return string|null + * + * @throws InvalidExpressCheckoutButtonStyleException + */ + private function normalizeButtonStyle(?string $buttonStyle): ?string + { + if ($buttonStyle === null || $buttonStyle === '') { + return null; + } + + if (\strlen($buttonStyle) > self::BUTTON_STYLE_MAX_BYTES || !\is_object(json_decode($buttonStyle))) { + throw new InvalidExpressCheckoutButtonStyleException(); + } + + return $buttonStyle; + } } diff --git a/tests/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutControllerTest.php b/tests/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutControllerTest.php index e96a3543..02d34410 100644 --- a/tests/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutControllerTest.php +++ b/tests/BusinessLogic/CheckoutAPI/ExpressCheckout/Controller/ExpressCheckoutControllerTest.php @@ -14,6 +14,7 @@ use SeQura\Core\BusinessLogic\Domain\CountryConfiguration\RepositoryContracts\CountryConfigurationRepositoryInterface; use SeQura\Core\BusinessLogic\Domain\CountryConfiguration\Services\CountryConfigurationService; use SeQura\Core\BusinessLogic\Domain\CountryConfiguration\Services\SellingCountriesService; +use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Models\ExpressCheckoutSettings; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\Services\ExpressCheckoutService; use SeQura\Core\BusinessLogic\Domain\ExpressCheckout\RepositoryContracts\ExpressCheckoutSettingsRepositoryInterface; use SeQura\Core\BusinessLogic\Domain\Order\Service\OrderService; @@ -81,7 +82,7 @@ public function testIsAvailableWrapsTrueResult(): void $response = CheckoutAPI::get()->expressCheckout('1')->isAvailable($this->buildRequest()); self::assertTrue($response->isSuccessful()); - self::assertSame(['available' => true], $response->toArray()); + self::assertSame(['available' => true, 'buttonStyle' => null], $response->toArray()); } /** @@ -93,7 +94,35 @@ public function testIsAvailableWrapsFalseResult(): void $response = CheckoutAPI::get()->expressCheckout('1')->isAvailable($this->buildRequest()); - self::assertSame(['available' => false], $response->toArray()); + self::assertSame(['available' => false, 'buttonStyle' => null], $response->toArray()); + } + + /** + * @return void + */ + public function testIsAvailableCarriesStoredButtonStyleVerbatim(): void + { + $buttonStyle = '{"color":"#00FF00","futureAttribute":"value"}'; + $this->expressCheckoutService->setAvailability(true); + $this->expressCheckoutService->saveExpressCheckoutSettings(new ExpressCheckoutSettings([], $buttonStyle)); + + $response = CheckoutAPI::get()->expressCheckout('1')->isAvailable($this->buildRequest()); + + self::assertSame(['available' => true, 'buttonStyle' => $buttonStyle], $response->toArray()); + } + + /** + * @return void + */ + public function testIsAvailableOmitsStoredButtonStyleWhenUnavailable(): void + { + $buttonStyle = '{"color":"#00FF00","futureAttribute":"value"}'; + $this->expressCheckoutService->setAvailability(false); + $this->expressCheckoutService->saveExpressCheckoutSettings(new ExpressCheckoutSettings([], $buttonStyle)); + + $response = CheckoutAPI::get()->expressCheckout('1')->isAvailable($this->buildRequest()); + + self::assertSame(['available' => false, 'buttonStyle' => null], $response->toArray()); } /** @@ -110,7 +139,10 @@ public function testIsAvailableForGuestReturnsAvailableWithCountries(): void $response = CheckoutAPI::get()->expressCheckout('1')->isAvailableForGuest($this->buildGuestRequest()); self::assertTrue($response->isSuccessful()); - self::assertSame(['available' => true, 'availableCountries' => ['ES', 'FR']], $response->toArray()); + self::assertSame( + ['available' => true, 'availableCountries' => ['ES', 'FR'], 'buttonStyle' => null], + $response->toArray() + ); } /** @@ -125,7 +157,10 @@ public function testIsAvailableForGuestReturnsUnavailableWhenGuardsFail(): void $response = CheckoutAPI::get()->expressCheckout('1')->isAvailableForGuest($this->buildGuestRequest()); - self::assertSame(['available' => false, 'availableCountries' => []], $response->toArray()); + self::assertSame( + ['available' => false, 'availableCountries' => [], 'buttonStyle' => null], + $response->toArray() + ); } /** @@ -137,7 +172,47 @@ public function testIsAvailableForGuestReturnsUnavailableWhenNoCountriesConfigur $response = CheckoutAPI::get()->expressCheckout('1')->isAvailableForGuest($this->buildGuestRequest()); - self::assertSame(['available' => false, 'availableCountries' => []], $response->toArray()); + self::assertSame( + ['available' => false, 'availableCountries' => [], 'buttonStyle' => null], + $response->toArray() + ); + } + + /** + * @return void + */ + public function testIsAvailableForGuestOmitsStoredButtonStyleWhenNoCountriesConfigured(): void + { + $buttonStyle = '{"color":"#00FF00","futureAttribute":"value"}'; + $this->expressCheckoutService->setGuestAvailability(true); + $this->expressCheckoutService->saveExpressCheckoutSettings(new ExpressCheckoutSettings([], $buttonStyle)); + + $response = CheckoutAPI::get()->expressCheckout('1')->isAvailableForGuest($this->buildGuestRequest()); + + self::assertSame( + ['available' => false, 'availableCountries' => [], 'buttonStyle' => null], + $response->toArray() + ); + } + + /** + * @return void + */ + public function testIsAvailableForGuestCarriesStoredButtonStyleVerbatim(): void + { + $buttonStyle = '{"color":"#00FF00","futureAttribute":"value"}'; + $this->expressCheckoutService->setGuestAvailability(true); + $this->expressCheckoutService->saveExpressCheckoutSettings(new ExpressCheckoutSettings([], $buttonStyle)); + $this->countryConfigurationService->saveCountryConfiguration([ + new CountryConfiguration('ES', 'merchant1'), + ]); + + $response = CheckoutAPI::get()->expressCheckout('1')->isAvailableForGuest($this->buildGuestRequest()); + + self::assertSame( + ['available' => true, 'availableCountries' => ['ES'], 'buttonStyle' => $buttonStyle], + $response->toArray() + ); } /** diff --git a/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php b/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php index 2813cba1..b88f3b1e 100644 --- a/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php +++ b/tests/BusinessLogic/ConfigurationWebhookAPI/ConfigurationWebhookAPITest.php @@ -2232,6 +2232,7 @@ public function testGetExpressCheckoutSettingsResponseNoSettings(): void self::assertEquals([ 'availablePages' => ['product', 'cart', 'mini-cart'], 'expressCheckoutConfigs' => [], + 'buttonStyle' => null, ], $response->toArray()); } @@ -2268,6 +2269,7 @@ public function testGetExpressCheckoutSettingsResponseWithPersistedConfigs(): vo ['page' => 'cart', 'enabled' => false], ['page' => 'mini-cart', 'enabled' => true], ], + 'buttonStyle' => null, ], $response->toArray()); } @@ -2298,6 +2300,7 @@ public function testSaveExpressCheckoutSettingsResponse(): void self::assertCount(2, $persisted->getExpressCheckoutConfigs()); self::assertTrue($persisted->isPageEnabled(ExpressCheckoutPage::product()->getPage())); self::assertFalse($persisted->isPageEnabled(ExpressCheckoutPage::cart()->getPage())); + self::assertNull($persisted->getButtonStyle()); } /** @@ -2335,4 +2338,215 @@ public function testSaveExpressCheckoutSettingsOverwritesPreviousValues(): void self::assertFalse($persisted->isPageEnabled(ExpressCheckoutPage::cart()->getPage())); self::assertTrue($persisted->isPageEnabled(ExpressCheckoutPage::miniCart()->getPage())); } + + /** + * @return void + * + * @throws InvalidEnvironmentException + */ + public function testSaveExpressCheckoutSettingsStoresUnknownButtonStyleAttributesVerbatim(): void + { + //Arrange + $buttonStyle = '{"attributeThisReleaseNeverHeardOf":"",' + . '"weird key":[1,2,{"deep":null}],"__proto__":" spaced "}'; + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-express-checkout-settings", + "expressCheckoutConfigs" => [ + ['page' => 'product', 'enabled' => true], + ], + "buttonStyle" => $buttonStyle, + ] + ); + + //Assert + self::assertTrue($response->isSuccessful()); + $persisted = $this->expressCheckoutSettingsService->getExpressCheckoutSettings(); + self::assertNotNull($persisted); + self::assertSame($buttonStyle, $persisted->getButtonStyle()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + */ + public function testSaveExpressCheckoutSettingsTreatsAnEmptyButtonStyleAsUnset(): void + { + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-express-checkout-settings", + "expressCheckoutConfigs" => [ + ['page' => 'product', 'enabled' => true], + ], + "buttonStyle" => '', + ] + ); + + //Assert + self::assertTrue($response->isSuccessful()); + $persisted = $this->expressCheckoutSettingsService->getExpressCheckoutSettings(); + self::assertNotNull($persisted); + self::assertNull($persisted->getButtonStyle()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + */ + public function testSaveExpressCheckoutSettingsRejectsMalformedButtonStyle(): void + { + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-express-checkout-settings", + "expressCheckoutConfigs" => [ + ['page' => 'product', 'enabled' => true], + ], + "buttonStyle" => '{"backgroundColor":', + ] + ); + + //Assert + self::assertFalse($response->isSuccessful()); + self::assertEquals([ + 'statusCode' => 400, + 'errorCode' => 'general.errors.expressCheckout.invalidButtonStyle', + 'errorMessage' => 'Invalid express checkout button style.', + 'errorParameters' => [], + ], $response->toArray()); + self::assertNull($this->expressCheckoutSettingsService->getExpressCheckoutSettings()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + */ + public function testSaveExpressCheckoutSettingsRejectsNonStringButtonStyle(): void + { + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-express-checkout-settings", + "expressCheckoutConfigs" => [], + "buttonStyle" => ['backgroundColor' => '#000000'], + ] + ); + + //Assert + self::assertFalse($response->isSuccessful()); + self::assertEquals( + 'general.errors.expressCheckout.invalidButtonStyle', + $response->toArray()['errorCode'] + ); + self::assertNull($this->expressCheckoutSettingsService->getExpressCheckoutSettings()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + */ + public function testExpressCheckoutButtonStyleRoundTripsThroughGetResponse(): void + { + //Arrange + $buttonStyle = '{"unknownFutureProp":"x","nested":{"a":[1,2]}}'; + ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-express-checkout-settings", + "expressCheckoutConfigs" => [ + ['page' => 'product', 'enabled' => true], + ], + "buttonStyle" => $buttonStyle, + ] + ); + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "get-express-checkout-settings" + ] + ); + + //Assert + self::assertTrue($response->isSuccessful()); + self::assertEquals([ + 'availablePages' => ['product', 'cart', 'mini-cart'], + 'expressCheckoutConfigs' => [ + ['page' => 'product', 'enabled' => true], + ], + 'buttonStyle' => $buttonStyle, + ], $response->toArray()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + */ + public function testSaveExpressCheckoutSettingsRejectsOversizedButtonStyle(): void + { + //Arrange + $buttonStyle = '{"backgroundColor":"' . str_repeat('a', 1515) . '"}'; + self::assertSame(1537, strlen($buttonStyle)); + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-express-checkout-settings", + "expressCheckoutConfigs" => [ + ['page' => 'product', 'enabled' => true], + ], + "buttonStyle" => $buttonStyle, + ] + ); + + //Assert + self::assertFalse($response->isSuccessful()); + self::assertEquals( + 'general.errors.expressCheckout.invalidButtonStyle', + $response->toArray()['errorCode'] + ); + self::assertNull($this->expressCheckoutSettingsService->getExpressCheckoutSettings()); + } + + /** + * @return void + * + * @throws InvalidEnvironmentException + */ + public function testSaveExpressCheckoutSettingsAcceptsButtonStyleAtTheSizeLimit(): void + { + //Arrange + $buttonStyle = '{"backgroundColor":"' . str_repeat('a', 1514) . '"}'; + self::assertSame(1536, strlen($buttonStyle)); + + //Act + $response = ConfigurationWebhookAPI::configurationHandler()->handleRequest( + $this->signature, + [ + "topic" => "save-express-checkout-settings", + "expressCheckoutConfigs" => [], + "buttonStyle" => $buttonStyle, + ] + ); + + //Assert + self::assertTrue($response->isSuccessful()); + $persisted = $this->expressCheckoutSettingsService->getExpressCheckoutSettings(); + self::assertNotNull($persisted); + self::assertSame($buttonStyle, $persisted->getButtonStyle()); + } } diff --git a/tests/BusinessLogic/DataAccess/ExpressCheckout/Entities/ExpressCheckoutSettingsTest.php b/tests/BusinessLogic/DataAccess/ExpressCheckout/Entities/ExpressCheckoutSettingsTest.php new file mode 100644 index 00000000..c2e819e2 --- /dev/null +++ b/tests/BusinessLogic/DataAccess/ExpressCheckout/Entities/ExpressCheckoutSettingsTest.php @@ -0,0 +1,72 @@ + 1, + 'storeId' => '1', + 'expressCheckoutSettings' => [ + 'expressCheckoutConfigs' => [ + ['page' => 'product', 'enabled' => true], + ], + 'buttonStyle' => '