simpler/magento-checkout-hyva

Hyvä theme compatibility layer for the Simpler Checkout button. Replaces the RequireJS/Knockout integration of simpler/magento-checkout with vanilla JS hooks into Hyvä's customer-data lifecycle.

Maintainers

Package info

gitlab.com/saysimpler/magento-modules/module-checkout-hyva

Issues

Documentation

Type:magento2-module

pkg:composer/simpler/magento-checkout-hyva

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

dev-main 2026-05-21 15:11 UTC

This package is not auto-updated.

Last update: 2026-05-22 13:23:40 UTC


README

Hyvä theme compatibility layer for Simpler Checkout. Replaces the RequireJS / Knockout integration of simpler/magento-checkout with vanilla JS hooks into Hyvä's customer-data lifecycle.

You can find extensive documentation on installing & configuring Simpler for Magento 2 here.

Installation

composer require simpler/magento-checkout-hyva
bin/magento module:enable Simpler_CheckoutHyva
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy

Install this only on storefronts running a Hyvä theme. The module activates automatically on Hyvä storefronts via the hyva-themes/magento2-compat-module-fallback mechanism — no theme switch is required. On Luma it stays out of the way.

What it does

Zero-config. Mirrors the base module's Luma behaviour on Hyvä, driven by a mix of layout XML (server-rendered surfaces Hyvä preserves) and a small JS runtime (PDP + session bridge):

SurfaceHow it renders
Product page (simple, virtual, configurable, bundle)JS fetches /simpler/config/product?productId=… on page load, builds a <simpler-checkout fullWidth="true">, inserts it as a full-width row before the .flex.mt-4.justify-end shortcut row inside the product-info .card. Falls back to appending to .card if the shortcut row is absent.
Product page — configurable swatch changeThe auto-injected element subscribes to Hyvä's configurable-selection-changed window event and re-fetches with the selected variant's id. payload updates live to reflect the chosen variant (Luma is static).
Cart drawer (minicart)Server-rendered via the compat module's layout/default.xml into Hyvä's extra_actions slot with fullWidth="true". JS keeps payload in sync on private-content-loaded (so the button reflects the live cart total inside the drawer).
Cart pageServer-rendered by the base module's checkout_cart_index.xml into checkout.cart.totals.container, which Hyvä preserves. The compat module does nothing extra here — JS only refreshes payload on cart change.
Checkout / success pagesServer-rendered by the base module. No compat-module involvement.

Magento config flags (simpler_checkout/settings/enable, show_in_product_view, show_in_cart_view) gate every surface — when false, the backend returns shouldRender: false (PDP) or the layout block is suppressed (cart/minicart) and nothing is mounted.

Architecture

Three pieces, all lightweight:

  • Controller/Config/Product.php (route /simpler/config/product) — JSON endpoint. Looks the product up via Magento\Catalog\Api\ProductRepositoryInterface, dispatches on $product->getTypeId() to the right base-module ViewModel (SimpleProductButton, ConfigurableProductButton, BundleProductButton), and returns {shouldRender, position, attrs: {appId, locale, payload, withAcceptedCards}} — identical shape to the existing /simpler/config/checkout. Payload computation is reused from the base module verbatim; no parallel logic.
  • view/frontend/templates/button.phtml — Hyvä-only override of the base module's button template (scoped via CompatModuleRegistry + ViewFileOverride). Early-returns for any simpler.product.page* position (those are JS-injected), and emits fullWidth="true" for the simpler.minicart position. All other positions (cart page, checkout, success, minicart) render through this template.
  • view/frontend/web/js/simpler-hyva.js — small ES2020 IIFE (no build step, no RequireJS). Loaded by head.phtml after the Simpler SDK. Responsibilities:
    1. Inject sessionId from window.hyva.getCustomerData('customer').simpler_hash onto every <simpler-checkout> element. A MutationObserver rewatches the body so elements Alpine clones into the cart drawer later also get their sessionId.
    2. Fetch /simpler/config/checkout on load and on every private-content-loaded, then apply {appId, locale, payload, withAcceptedCards} to all simpler-checkout[position="simpler.minicart"] and simpler-checkout[position="simpler.cart.page"] elements in the DOM. This keeps the server-rendered minicart and cart-page buttons reflecting the live cart.
    3. Fetch /simpler/config/product?productId=… on PDPs, build a <simpler-checkout fullWidth="true">, anchor it in the product card (see selector contract below), and subscribe to configurable-selection-changed to refresh payload and position on variant change.

Loader URLs flow into JS via data-checkout-url / data-product-url attributes on the <script id="simpler-hyva-loader"> tag emitted by head.phtml. No hard-coded paths; getUrl() handles base-URL and rewrites per store.

Selector contract (PDP auto-injection)

The PDP auto-injection in simpler-hyva.js::initPdp() reads exactly three DOM landmarks from the default Hyvä product-info.phtml. If your child theme renames or removes any of them, behaviour degrades as listed:

SelectorUsed asIf missing
#product_addtocart_form input[name="product"]Source of the productId to send to /simpler/config/product.initPdp() returns early — no button on PDP.
#product-addtocart-buttonAnchor whose .closest('.card') ancestor scopes all subsequent queries. Hyvä's product-info form contains only hidden inputs, so the ATC button itself lives outside <form> (associated via form="…"). The .card ancestor is the only stable scope.initPdp() returns early.
.flex.mt-4.justify-end (inside the card)Sibling anchor — the wrapper is inserted before it as a full-width row.Falls back to card.appendChild(wrapper) — element appears at the bottom of the card instead of directly under the ATC button.

The wrapper itself is <div class="mt-4 w-full"> and the <simpler-checkout> gets inline display: block; width: 100% plus the fullWidth="true" attribute (which toggles the SDK's full-width style on the rendered button).

If you've rebuilt product-info.phtml and want the auto-injection to keep working, the minimum contract is: keep an element with id="product-addtocart-button" somewhere inside a .card wrapper. The shortcut-row anchor is optional (graceful fallback).

Customising for a child theme

Three escape hatches, ordered from least to most invasive.

1. Hide a surface entirely

Magento admin → Stores → Configuration → Simpler Checkout:

  • show_in_product_view → No → PDP endpoint returns shouldRender: false, JS skips injection.
  • show_in_cart_view → No → both the minicart-drawer block (compat module) and the cart-page block (base module) are suppressed server-side.
  • simpler_checkout/settings/enable → No → kills everything.

2. Re-anchor the PDP button yourself

When you want Simpler on PDP but in a different spot than the default JS placement:

<!-- in your child theme's product-info.phtml or a fragment of your choice -->
<simpler-checkout
    position="simpler.product.page"
    appId="<?= $block->escapeHtmlAttr($simplerAppId) ?>"
    locale="<?= $block->escapeHtmlAttr($locale) ?>"
    withAcceptedCards="true"
    fullWidth="true"
    payload="<?= $block->escapeHtmlAttr($payload) ?>">
</simpler-checkout>

Compute appId / locale / payload from one of the base-module ViewModels (Simpler\Checkout\ViewModel\SimpleProductButton, ConfigurableProductButton, BundleProductButton). The compat module's MutationObserver will pick up your element and inject sessionId automatically — no custom JS needed for the session bridge.

Two caveats:

  • The default JS auto-injection will also run if you leave show_in_product_view = Yes. Either flip the config to No (and accept that no surface gets auto-wiring) or hide the auto-injected element with a CSS rule scoped to its specific wrapper.
  • The configurable-selection-changed listener is attached only to the auto-injected element. A hand-rolled embed on a configurable PDP will have a static payload (whichever variant was selected at page load). If you need live variant updates, add an Alpine listener that re-fetches /simpler/config/product?productId=<variantId> and writes the new payload attribute onto your element.

3. Render in a non-PDP / non-cart surface

For sticky add-to-cart bars, CMS blocks, custom Hyvä pages, etc. — drop the same <simpler-checkout> snippet in. The JS doesn't care where the element is; the MutationObserver covers it for sessionId, and if the position is simpler.minicart or simpler.cart.page the cart-config refresh applies attrs automatically.

You can also call the JSON endpoints directly from your own Alpine component:

const productData = await fetch('/simpler/config/product?productId=' + productId)
    .then(r => r.json());
// productData.attrs.{appId, locale, payload, withAcceptedCards}, productData.position

const cartData = await fetch('/simpler/config/checkout')
    .then(r => r.json());
// cartData.attrs.{appId, locale, payload, withAcceptedCards}

How the Hyvä bridge wires up

  • Frontend-area etc/frontend/di.xml registers the pair Simpler_CheckoutHyvaSimpler_Checkout with Hyva\CompatModuleFallback\Model\CompatModuleRegistry. The compat-fallback's ViewFileOverride plugin makes our view/frontend/templates/head.phtml and templates/button.phtml win over the base module's equivalents — but only when Hyva\Theme\Service\CurrentTheme::isHyva() returns true. On Luma, the base module's templates stay.
  • etc/frontend/routes.xml adds the compat module under the existing simpler frontname so /simpler/config/product resolves to Simpler_CheckoutHyva\Controller\Config\Product.
  • The minicart drawer block in view/frontend/layout/default.xml targets Hyvä's extra_actions container. That container doesn't exist on Luma, so the layout instruction is a silent no-op there.
  • No events.xml, no crontab.xml, no observers — the module is a pure rendering bridge.