From ea066cc1b217df32fc11d127c5a71747c087f2a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20Escalante=20=C3=81lvarez?= Date: Wed, 19 Aug 2026 16:41:08 +0200 Subject: [PATCH 1/4] feat(express-checkout): pass the merchant button style through to the button The blob is opaque here: only the portal that writes it and the button that renders it know its attribute names, so this module forwards it untouched and a new style property needs no release of this module or any other plugin. The attribute is omitted rather than emitted empty, so an unconfigured store takes the library's existing default path. Co-Authored-By: Claude Opus 5 (1M context) --- .../AbstractExpressCheckoutBlock.php | 23 ++++++++++- Block/ExpressCheckout/ProductPage.php | 23 ++++++++++- .../ExpressCheckout/AvailabilityEvaluator.php | 39 +++++++++++++++---- view/frontend/templates/express/cart.phtml | 4 ++ .../frontend/templates/express/minicart.phtml | 4 ++ view/frontend/templates/express/product.phtml | 4 ++ 6 files changed, 87 insertions(+), 10 deletions(-) diff --git a/Block/ExpressCheckout/AbstractExpressCheckoutBlock.php b/Block/ExpressCheckout/AbstractExpressCheckoutBlock.php index 3a1a386..c353d13 100644 --- a/Block/ExpressCheckout/AbstractExpressCheckoutBlock.php +++ b/Block/ExpressCheckout/AbstractExpressCheckoutBlock.php @@ -46,6 +46,12 @@ abstract class AbstractExpressCheckoutBlock extends Template * @var string|null */ private ?string $state = null; + /** + * Memoized merchant-configured button style blob for the current request. + * + * @var string|null + */ + private ?string $buttonStyle = null; /** * @param ScopeResolverInterface $scopeResolver @@ -109,6 +115,18 @@ public function getUnavailableMessage(): string return (string)__('SeQura is not available for your account.'); } + /** + * The merchant-configured button style blob, forwarded verbatim to the checkout library. + * + * @return string|null + */ + public function getButtonStyle(): ?string + { + $this->resolveState(); + + return $this->buttonStyle; + } + /** * The storefront solicit URL the CDN-library button fetches (GET, raw HTML response). * @@ -161,7 +179,7 @@ private function resolveState(): string ? (string)$this->shippingResolver->getResolvableShippingCountry($quote) : ''; - $this->state = $this->availabilityEvaluator->evaluate( + $result = $this->availabilityEvaluator->evaluate( (string)$this->_storeManager->getStore()->getId(), $this->getExpressCheckoutPage(), $isLoggedIn, @@ -172,6 +190,9 @@ private function resolveState(): string $this->getCartCategoryIds($quote) ); + $this->state = $result['state']; + $this->buttonStyle = $result['buttonStyle']; + return $this->state; } catch (Exception $e) { Logger::logError('Checking Express Checkout availability failed: ' . $e->getMessage() . diff --git a/Block/ExpressCheckout/ProductPage.php b/Block/ExpressCheckout/ProductPage.php index 86e17d2..93626c3 100644 --- a/Block/ExpressCheckout/ProductPage.php +++ b/Block/ExpressCheckout/ProductPage.php @@ -58,6 +58,12 @@ class ProductPage extends Template * @var string|null */ private ?string $state = null; + /** + * Memoized merchant-configured button style blob for the current request. + * + * @var string|null + */ + private ?string $buttonStyle = null; /** * Memoized product for the current request (null also means "resolved to nothing"). * @@ -109,6 +115,18 @@ public function isAvailable(): bool return $this->resolveState() === AvailabilityEvaluator::STATE_BUTTON; } + /** + * The merchant-configured button style blob, forwarded verbatim to the checkout library. + * + * @return string|null + */ + public function getButtonStyle(): ?string + { + $this->resolveState(); + + return $this->buttonStyle; + } + /** * The viewed product's entity ID, for the JS controller. * @@ -171,7 +189,7 @@ private function resolveState(): string // Always the guest (customer-agnostic) evaluation: the page is full-page cached, so // the session is depersonalized during render and the HTML is shared by all shoppers. - $this->state = $this->availabilityEvaluator->evaluate( + $result = $this->availabilityEvaluator->evaluate( (string)$this->_storeManager->getStore()->getId(), ExpressCheckoutPage::product()->getPage(), false, @@ -182,6 +200,9 @@ private function resolveState(): string $this->getProductCategoryIds($product) ); + $this->state = $result['state']; + $this->buttonStyle = $result['buttonStyle']; + return $this->state; } catch (Exception $e) { Logger::logError('Checking Express Checkout availability for product failed: ' . $e->getMessage() . diff --git a/Model/ExpressCheckout/AvailabilityEvaluator.php b/Model/ExpressCheckout/AvailabilityEvaluator.php index d12bd62..b7930ba 100644 --- a/Model/ExpressCheckout/AvailabilityEvaluator.php +++ b/Model/ExpressCheckout/AvailabilityEvaluator.php @@ -48,7 +48,7 @@ class AvailabilityEvaluator * @param string[] $productIds Product entity IDs in context. * @param string[] $categoryIds Category IDs in context. * - * @return string One of self::STATE_*. + * @return array{state: string, buttonStyle: string|null} State is one of self::STATE_*. */ public function evaluate( string $storeId, @@ -59,7 +59,7 @@ public function evaluate( string $ipAddress, array $productIds, array $categoryIds - ): string { + ): array { try { if ($isLoggedIn) { /** @var ExpressCheckoutAvailabilityResponse $response */ @@ -74,9 +74,13 @@ public function evaluate( ) ); - $available = $response->isSuccessful() && !empty($response->toArray()['available']); + $data = $response->toArray(); + $available = $response->isSuccessful() && !empty($data['available']); - return $available ? self::STATE_BUTTON : self::STATE_MESSAGE; + return $this->result( + $available ? self::STATE_BUTTON : self::STATE_MESSAGE, + $data['buttonStyle'] ?? null + ); } /** @var GuestExpressCheckoutAvailabilityResponse $response */ @@ -90,14 +94,33 @@ public function evaluate( ) ); - return ($response->isSuccessful() && !empty($response->toArray()['available'])) - ? self::STATE_BUTTON - : self::STATE_HIDDEN; + $data = $response->toArray(); + + return $this->result( + ($response->isSuccessful() && !empty($data['available'])) ? self::STATE_BUTTON : self::STATE_HIDDEN, + $data['buttonStyle'] ?? null + ); } catch (Exception $e) { Logger::logError('Checking Express Checkout availability failed: ' . $e->getMessage() . ' Trace: ' . $e->getTraceAsString()); - return self::STATE_HIDDEN; + return $this->result(self::STATE_HIDDEN, null); } } + + /** + * Builds the evaluator result. + * + * @param string $state + * @param mixed $buttonStyle Opaque style blob from the core response, passed through untouched. + * + * @return array{state: string, buttonStyle: string|null} + */ + private function result(string $state, $buttonStyle): array + { + return [ + 'state' => $state, + 'buttonStyle' => is_string($buttonStyle) ? $buttonStyle : null, + ]; + } } diff --git a/view/frontend/templates/express/cart.phtml b/view/frontend/templates/express/cart.phtml index 2dc818e..eecd308 100644 --- a/view/frontend/templates/express/cart.phtml +++ b/view/frontend/templates/express/cart.phtml @@ -3,9 +3,13 @@ /** @var \Magento\Framework\Escaper $escaper */ if ($block->isAvailable()): + $buttonStyle = (string)$block->getButtonStyle(); ?>
+ data-button-style="escapeHtmlAttr($buttonStyle); ?>" + data-url="escapeHtmlAttr($block->getSolicitUrl()); ?>">