sylius / live-one-page-checkout-plugin
LiveOnePageCheckout plugin for Sylius
Package info
github.com/Sylius/LiveOnePageCheckoutPlugin
Type:sylius-plugin
pkg:composer/sylius/live-one-page-checkout-plugin
Fund package maintenance!
Requires
- php: ^8.2
- sylius/sylius: ^2.2
- sylius/telemetry: ^1.0
Requires (Dev)
- behat/behat: ^3.22 || 4.0.0-alpha1
- behat/mink: ^1.10 || ^1.13
- dbrekelmans/bdi: ^1.4
- dmore/chrome-mink-driver: ^2.9
- friends-of-behat/mink-browserkit-driver: ^1.6.3
- friends-of-behat/mink-debug-extension: ^2.2
- friends-of-behat/mink-extension: ^2.7 || 3.0.0-alpha1
- friends-of-behat/page-object-extension: ^0.3
- friends-of-behat/symfony-extension: ^2.6.2
- friends-of-behat/variadic-extension: ^1.6
- lchrusciel/api-test-case: ^5.0 || ^6.0
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.1
- phpstan/phpstan-doctrine: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^10.5
- sylius-labs/behat-chrome-extension: ^1.5
- sylius-labs/coding-standard: ^4.4
- sylius-labs/suite-tags-extension: ~0.2
- sylius/sylius-rector: ^2.0 || ^3.0
- sylius/test-application: ^2.0.0@alpha
- symfony/browser-kit: ^6.4 || ^7.4 || ^8.0
- symfony/debug-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/dotenv: ^6.4 || ^7.4 || ^8.0
- symfony/http-client: ^6.4 || ^7.4 || ^8.0
- symfony/intl: ^6.4 || ^7.4 || ^8.0
- symfony/runtime: ^6.4 || ^7.4 || ^8.0
- symfony/web-profiler-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/webpack-encore-bundle: ^2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-11-06 06:19:07 UTC
README
Sylius Live One Page Checkout Plugin
Replace the default multi-step checkout with a single, live-updating one page checkout. Cart, addresses, shipping, payment and order summary live on one page and update as the customer edits them - enabled per channel, so other channels keep the standard Sylius checkout.
What it does
- Puts cart, addresses, shipping, payment and order summary on one page, updating live via Symfony UX Live Components - no full page reloads as the customer fills the form.
- Per-channel toggle: enable "one page checkout" on a channel in the admin panel and only that channel switches to the one-page flow; every other channel keeps the standard Sylius checkout.
- Adds a direct
cart → completedcheckout transition (guarded so it only fires on enabled channels), works with both the Symfony Workflow and winzou state machines. - Protects against stale submissions with an order state hash: if totals, promotions, shipping or payment changed between render and submit, the customer is sent back to review.
See CLAUDE.md for a full architecture breakdown.
Requirements
| Package | Version |
|---|---|
| PHP | ^8.2 |
sylius/sylius |
^2.2 |
⚙️ Installation
This guide covers the Live One Page Checkout plugin installation process using Flex recipes and Rector.
Prerequisites
You need to have installed SyliusRector. You can find how to install it here:
Installation guide
-
Run
composer require sylius/live-one-page-checkout-plugin --no-scripts
-
Run
vendor/bin/rector
-
Ensure the modified resources are configured in
config/packages/_sylius.yaml:sylius_channel: resources: channel: classes: model: App\Entity\Channel\Channel sylius_order: resources: order: classes: model: App\Entity\Order\Order
-
Execute migrations to add the
one_page_checkout_enabledcolumn:bin/console doctrine:migrations:migrate
-
Run
yarn encore devoryarn encore productionThe recipe registers the bundle, imports the plugin config and routing, and adds the plugin's Shop assets import to
assets/shop/entrypoint.jsfor you, so there is nothing to wire by hand. -
If your application registers custom order processors that affect price, promotions, shipping or taxes, tag them accordingly - see Custom order processors.
-
Rebuild the cache to display all new translations correctly:
bin/console cache:clear
-
In the admin panel, edit a channel and tick Enable one page checkout to switch it to the one page flow.
⌛️ Legacy installation
Don't want to use recipes or Rector? You can still follow the legacy installation guide below:
-
Run
composer require sylius/live-one-page-checkout-plugin --no-scripts
or
composer require sylius/live-one-page-checkout-plugin --no-scripts
-
Add the following line to
config/bundles.php:Sylius\LiveOnePageCheckoutPlugin\SyliusLiveOnePageCheckoutPlugin::class => ['all' => true],
-
The
Channelentity should implement the following interface:Sylius\LiveOnePageCheckoutPlugin\Entity\ChannelOnePageCheckoutAwareInterface
and use trait:
Sylius\LiveOnePageCheckoutPlugin\Entity\ChannelOnePageCheckoutAwareTrait
Final result:
<?php declare(strict_types=1); namespace App\Entity\Channel; use Doctrine\ORM\Mapping as ORM; use Sylius\Component\Core\Model\Channel as BaseChannel; use Sylius\LiveOnePageCheckoutPlugin\Entity\ChannelOnePageCheckoutAwareInterface; use Sylius\LiveOnePageCheckoutPlugin\Entity\ChannelOnePageCheckoutAwareTrait; #[ORM\Entity] #[ORM\Table(name: 'sylius_channel')] class Channel extends BaseChannel implements ChannelOnePageCheckoutAwareInterface { use ChannelOnePageCheckoutAwareTrait; }
-
Add configuration of the modified channel resource in
config/packages/_sylius.yaml:sylius_channel: resources: channel: classes: model: App\Entity\Channel\Channel
-
The
Orderentity should implement the following interface:Sylius\LiveOnePageCheckoutPlugin\Entity\OrderAndItemPromotionTotalAwareInterface
and use traits:
Sylius\LiveOnePageCheckoutPlugin\Entity\OrderAndItemPromotionTotalAwareTrait Sylius\LiveOnePageCheckoutPlugin\Entity\OrderShippingTaxTotalTrait
OrderShippingTaxTotalTraitoverridesgetShippingTaxTotal(), which in Sylius core sums the shipping charge instead of the tax charged on it.Final result:
<?php declare(strict_types=1); namespace App\Entity\Order; use Doctrine\ORM\Mapping as ORM; use Sylius\Component\Core\Model\Order as BaseOrder; use Sylius\LiveOnePageCheckoutPlugin\Entity\OrderAndItemPromotionTotalAwareInterface; use Sylius\LiveOnePageCheckoutPlugin\Entity\OrderAndItemPromotionTotalAwareTrait; use Sylius\LiveOnePageCheckoutPlugin\Entity\OrderShippingTaxTotalTrait; #[ORM\Entity] #[ORM\Table(name: 'sylius_order')] class Order extends BaseOrder implements OrderAndItemPromotionTotalAwareInterface { use OrderAndItemPromotionTotalAwareTrait; use OrderShippingTaxTotalTrait; }
-
Add configuration of the modified order resource in
config/packages/_sylius.yaml:sylius_order: resources: order: classes: model: App\Entity\Order\Order
-
Import the plugin configuration in
config/packages/_sylius.yaml:imports: - { resource: "@SyliusLiveOnePageCheckoutPlugin/config/config.yaml" }
-
Configure routing in
config/routes/sylius_live_one_page_checkout.yaml:sylius_live_one_page_checkout: resource: "@SyliusLiveOnePageCheckoutPlugin/config/routes.yaml"
-
Import the plugin's Shop assets into your application's Shop entry file:
// assets/shop/entrypoint.js import '@vendor/sylius/live-one-page-checkout-plugin/assets/shop/entrypoint';
-
Execute migrations to add the
one_page_checkout_enabledcolumn:bin/console doctrine:migrations:migrate
-
Run
yarn encore devoryarn encore production -
If your application registers custom order processors that affect price, promotions, shipping or taxes, tag them accordingly - see Custom order processors.
-
Rebuild the cache to display all new translations correctly:
bin/console cache:clear
Mollie integration
When your Sylius application has SyliusMolliePlugin installed and registered, the plugin automatically:
- Adds Mollie payment submethods (iDEAL, credit card, etc.) to the OPC payment form via
PaymentTypeExtension. - Renders the submethod selector and the Mollie credit card widget via twig hooks.
The JavaScript must be wired up manually in your project (the entry point name and webpack package are project-specific).
JavaScript setup
Option A - OPC-only (recommended): add a dedicated webpack entry and register a twig hook so the script loads only on OPC checkout pages.
// webpack.config.js - inside your shop Encore block Encore.addEntry('opc-mollie-shop', './vendor/sylius/live-one-page-checkout-plugin/assets/shop/integration/mollie/entrypoint.js');
# config/packages/sylius_twig_hooks.yaml sylius_twig_hooks: hooks: 'sylius_live_one_page_checkout.shop.checkout#javascripts': opc_mollie: template: 'shop/checkout/one_page/mollie_js.html.twig' priority: 0
{# templates/shop/checkout/one_page/mollie_js.html.twig #} {{ encore_entry_script_tags('opc-mollie-shop', null, 'app.shop') }}
Adjust the entry name (opc-mollie-shop) and the Encore package name (app.shop) to match your
project's webpack configuration.
Option B - global (simpler setup): import the script directly in your existing shop entrypoint.
The script self-guards via .sylius-one-page-checkout and will not initialise on any other page.
// assets/shop/entrypoint.js import '@vendor/sylius/live-one-page-checkout-plugin/assets/shop/integration/mollie/entrypoint';
This plain-JS file handles show/hide of Mollie submethods and the Mollie.js credit card component
on the OPC page. It loads https://js.mollie.com/v1/mollie.js on demand (only when the customer
selects the credit card method) and re-initialises automatically after each Live Component re-render.
Custom order processors
The plugin does not reuse Sylius' core order processor composite (sylius.order_processing.order_processor.composite)
for its live recalculation. Instead it builds its own composite,
sylius_live_one_page_checkout.order_processing.order_processor.composite, populated only with an
explicit whitelist of core processors (adjustments clearer, prices recalculator, shipping charges,
promotion, taxes, checkout payment) - see SyliusLiveOnePageCheckoutPlugin::build(). Notably it
excludes the core shipping-method default-setter processor, since auto-selecting a shipping
method there conflicts with the live-updating one-page UI.
This means: if your application registers a custom order processor that affects the price,
promotions, shipping or taxes (i.e. anything that ends up in the order state hash, see
Provider/OrderHashDataProvider), tagging it with the core sylius.order_processor tag alone is
not enough for the one-page checkout. You must also tag it with
sylius_live_one_page_checkout.order_processor (with an appropriate priority) so it's included in
the plugin's composite:
services: App\OrderProcessing\MyCustomProcessor: tags: - { name: sylius.order_processor, priority: 15 } - { name: sylius_live_one_page_checkout.order_processor, priority: 15 }
Without this, the Live Component's FormComponent will compute totals/hash without your
processor's effect, while OrderIntegrityChecker (which still uses the full core composite) will
compute them with it - causing a false state_mismatch redirect on every checkout submission.
API
The plugin exposes the same one-page checkout flow over Sylius' Shop API (API Platform), so a headless/mobile client can drive it without the Live Component.
POST /shop/orders/{tokenValue}/one-page/preview
Applies the submitted form data (address, selected shipping/payment method, coupon) to the cart
without persisting anything, recalculates the order (prices, promotions, taxes, available
shipping/payment methods) and returns a snapshot plus an anti-tamper hash. Call it on every
form change, the same way the shop FormComponent recalculates on every re-render.
The response (CheckoutPreview) only contains data that can change between renders: order
summary totals, per-item prices (unitPrice, originalUnitPrice, discountedUnitPrice,
subtotal, total), addresses, available shipping/payment methods and the state hash. It
intentionally does not repeat data that stays constant for the whole checkout (item name,
image, quantity) - see below.
POST /shop/orders/{tokenValue}/one-page/complete
Finalizes the checkout: re-validates the submitted hash against the order's current state
(rejects stale submissions the same way the shop flow does), transitions the order to
completed and returns the full Order resource (sylius:shop:cart:show group).
GET /shop/orders/{tokenValue} - constant order data
This is Sylius core's own endpoint, no plugin-specific route needed. Use it once (e.g. on page
load) to get everything that does not change while the customer edits the checkout form:
channel, currency, locale, customer and the order items (id, productName, variantName,
quantity). Re-fetch prices from preview instead of polling this endpoint.
On channels with one page checkout enabled, each order item in the response also gets an
extra image field: an absolute, cached thumbnail URL resolved the same way the checkout
summary does (variant images if the variant has any, otherwise the product's images,
filtered to the thumbnail type). Pass ?imageFilter=<filter_name> to request a different Liip
Imagine filter than the default (sylius_shop_product_small_thumbnail). On channels with the
feature disabled, the response is byte-for-byte identical to stock Sylius - no image field
is added.
License
Released under the Business Source License 1.1. The plugin is source-available, not open source, and each version converts to the MIT License four years after it is published.
Production use is free as long as Sylius telemetry is not deliberately disabled and the plugin is
not used to provide a competing offering. Setting any of the required telemetry settings to 0
(e.g. SYLIUS_TELEMETRY_BUSINESS=0) in production breaches this condition: you need a commercial
license. See the
License FAQ and the Licensor Statement.
For commercial licensing, contact contact@sylius.com.