diff --git a/development/components/console/prestashop-employee-change-password.md b/development/components/console/prestashop-employee-change-password.md new file mode 100644 index 0000000000..5fc5e56a20 --- /dev/null +++ b/development/components/console/prestashop-employee-change-password.md @@ -0,0 +1,40 @@ +--- +title: prestashop:employee:change-password +category: Utilities +description: Reset an existing employee's password from the CLI +weight: 31 +--- + +# `prestashop:employee:change-password` + +{{< minver v="9.2" title="true" >}} + +## Informations + +* Path: `src/PrestaShopBundle/Command/EmployeeChangePasswordCommand.php` +* Arguments: + * `email`: Employee email __(optional, prompted when omitted)__ +* Options: + * `--password`: New password. Prefer the interactive prompt to avoid leaking it in your shell history __(optional, prompted when omitted)__ + +## Description + +This command resets the password of an existing back-office employee from the command line. By default it is interactive and prompts for the email and the new password (entered twice). Passing the email argument and the `--password` option runs it non-interactively. + +On success, the employee receives the "Your new password" email, the same template used by the Back Office forgot-password flow. + +To provision a new SuperAdmin instead, use [`prestashop:employee:create-admin`]({{< relref "prestashop-employee-create-admin" >}}). + +## Examples + +### Reset a password interactively + +```bash +$ bin/console prestashop:employee:change-password +``` + +### Reset a password non-interactively + +```bash +$ bin/console prestashop:employee:change-password admin@example.com --password='S0meStr0ngP@ss!' +``` diff --git a/development/components/console/prestashop-employee-create-admin.md b/development/components/console/prestashop-employee-create-admin.md new file mode 100644 index 0000000000..3ef9a1f1fd --- /dev/null +++ b/development/components/console/prestashop-employee-create-admin.md @@ -0,0 +1,43 @@ +--- +title: prestashop:employee:create-admin +category: Utilities +description: Create a back-office SuperAdmin employee from the CLI +weight: 30 +--- + +# `prestashop:employee:create-admin` + +{{< minver v="9.2" title="true" >}} + +## Informations + +* Path: `src/PrestaShopBundle/Command/EmployeeCreateCommand.php` +* Arguments: + * `email`: Employee email __(optional, prompted when omitted)__ +* Options: + * `--first-name`: First name __(optional, prompted when omitted)__ + * `--last-name`: Last name __(optional, prompted when omitted)__ + * `--password`: Password. Prefer the interactive prompt to avoid leaking it in your shell history __(optional, prompted when omitted)__ + +## Description + +This command provisions a new back-office **SuperAdmin** employee from the command line. It is intended for first-admin provisioning and recovery, not for generic employee management: use the *Team* page in the Back Office for that. + +The created account is always a SuperAdmin with full back-office access, active, on the default language, and associated to every shop. By default the command is interactive and prompts for the missing values. Passing all values as arguments and options runs it non-interactively, which is convenient for CI or automated provisioning. + +To reset the password of an existing employee, use [`prestashop:employee:change-password`]({{< relref "prestashop-employee-change-password" >}}). + +## Examples + +### Create a SuperAdmin interactively + +```bash +$ bin/console prestashop:employee:create-admin +``` + +### Create a SuperAdmin non-interactively + +```bash +$ bin/console prestashop:employee:create-admin john@example.com \ + --first-name=John --last-name=Doe --password='Str0ng!Pass' +``` diff --git a/development/components/console/prestashop-htaccess-generate.md b/development/components/console/prestashop-htaccess-generate.md new file mode 100644 index 0000000000..7ee5fc3288 --- /dev/null +++ b/development/components/console/prestashop-htaccess-generate.md @@ -0,0 +1,36 @@ +--- +title: prestashop:htaccess:generate +category: Utilities +description: Regenerate the .htaccess file from the CLI +weight: 32 +--- + +# `prestashop:htaccess:generate` + +{{< minver v="9.2" title="true" >}} + +## Informations + +* Path: `src/PrestaShopBundle/Command/GenerateHtaccessCommand.php` +* Options: + * `--force` (`-f`): Force overwrite even if the file already exists __(optional)__ + +## Description + +This command regenerates the `.htaccess` file directly from the command line, without accessing the Back Office. This provides a faster and more consistent way to regenerate `.htaccess`, for example in deployment workflows. + +If the `.htaccess` file already exists, the command warns and stops unless `--force` is passed. With `--force`, the existing file is overwritten. + +## Examples + +### Generate the .htaccess file + +```bash +$ bin/console prestashop:htaccess:generate +``` + +### Overwrite an existing .htaccess file + +```bash +$ bin/console prestashop:htaccess:generate --force +``` diff --git a/development/components/console/prestashop-module-list.md b/development/components/console/prestashop-module-list.md new file mode 100644 index 0000000000..a8f2bbee9d --- /dev/null +++ b/development/components/console/prestashop-module-list.md @@ -0,0 +1,54 @@ +--- +title: prestashop:module:list +category: Module Management +description: List shop modules with their version and status +weight: 20 +--- + +# `prestashop:module:list` + +{{< minver v="9.2" title="true" >}} + +## Informations + +* Path: `src/PrestaShopBundle/Command/ModuleListCommand.php` +* Options: + * `--active`: Show only installed and enabled modules __(optional)__ + * `--disabled`: Show only installed but disabled modules __(optional)__ + * `--not-installed`: Show only modules present on disk or registered via the `actionListModules` hook but not installed __(optional)__ + * `--all`: Include uninstalled modules alongside installed ones __(optional)__ + * `--simple`: Output only technical names, one per line, instead of a table __(optional)__ + +## Description + +This command lists shop modules from the command line. By default it prints a table of installed modules with their name, version, and status (`Enabled` or `Disabled`), sorted alphabetically. + +The scope options `--active`, `--disabled`, `--not-installed`, and `--all` are mutually exclusive: passing more than one returns an error and a non-zero exit code. The `--simple` flag is orthogonal and composes with any scope filter, which makes it convenient for `grep`, `awk`, or `xargs` pipelines. + +The existing [`prestashop:module`]({{< relref "prestashop-module" >}}) command is left unchanged. + +## Examples + +### List all installed modules + +```bash +$ bin/console prestashop:module:list +``` + +### List only disabled modules + +```bash +$ bin/console prestashop:module:list --disabled +``` + +### List technical names of disabled modules, one per line + +```bash +$ bin/console prestashop:module:list --simple --disabled +``` + +### List installed and not-installed modules together + +```bash +$ bin/console prestashop:module:list --all +``` diff --git a/development/components/console/prestashop-thumbnails-regenerate.md b/development/components/console/prestashop-thumbnails-regenerate.md index 2fa75e5ac6..860ee9f84e 100644 --- a/development/components/console/prestashop-thumbnails-regenerate.md +++ b/development/components/console/prestashop-thumbnails-regenerate.md @@ -7,6 +7,8 @@ weight: 10 # `prestashop:thumbnails:regenerate` +{{< minver v="9.1" title="true" >}} + ## Informations * Path: `src/PrestaShopBundle/Command/RegenerateThumbnailsCommand.php` @@ -18,8 +20,6 @@ weight: 10 ## Description -Since {{< minver v="9.1.x" >}}. - This command aims to regenerate thumbnails via command line. ## Examples diff --git a/modules/checkout/_index.md b/modules/checkout/_index.md new file mode 100644 index 0000000000..0d2f39253b --- /dev/null +++ b/modules/checkout/_index.md @@ -0,0 +1,16 @@ +--- +title: Checkout +menuTitle: Checkout +weight: 32 +chapter: true +--- + +# Checkout + +PrestaShop {{< minver v="9.2" >}} includes a native One Page Checkout (OPC) module in the bundle, which brings the entire checkout experience together onto a single page. + +This section covers what module and theme developers need to know to make their solutions compatible with the One Page Checkout: + +{{% children /%}} + +{{% notice info %}} This documentation is being continuously improved. If you notice any missing or incomplete information, please help us by creating an issue on our [GitHub repository](https://github.com/PrestaShop/docs/issues/new). {{% /notice %}} diff --git a/modules/checkout/module-developers.md b/modules/checkout/module-developers.md new file mode 100644 index 0000000000..e2b25654d2 --- /dev/null +++ b/modules/checkout/module-developers.md @@ -0,0 +1,363 @@ +--- +title: One Page Checkout for module developers +menuTitle: For module developers +weight: 10 +--- + +# One Page Checkout for module developers + +{{< minver v="9.2" title="true" >}} + +PrestaShop 9.2 introduces a new native One Page Checkout module: `ps_onepagecheckout`. + +This page explains how the module works, how it integrates with the checkout process, and what module developers need to know to ensure compatibility with stores using One Page Checkout. It covers the checkout architecture, the available extension points, and the changes that may affect payment, carrier, checkout customization, and other modules interacting with the checkout flow. + +{{% notice tip %}} The guidance on this page applies to any module that interacts with the checkout, not only payment modules. If you build payment or carrier modules, jump straight to [Payment modules compatibility](#payment-modules-compatibility) or [Carrier modules compatibility](#carrier-modules-compatibility). {{% /notice %}} + +## What is ps_onepagecheckout? + +`ps_onepagecheckout` is the native One Page Checkout module introduced in PrestaShop 9.2. When enabled, it replaces the standard multi-step checkout with a single-page checkout experience that combines customer information, delivery options, and payment methods into a unified flow. + +The module is included with PrestaShop 9.2. While installed by default, the One Page Checkout experience is not enabled automatically. Merchants must enable and configure it before it becomes active on the front office. + +If your module interacts with the checkout process, displays content during checkout, or modifies checkout behavior, you should review the compatibility considerations described on this page. + +## Module architecture + +The module follows a clean, handler-based architecture. Here is an overview of the key layers. + +### Checkout process + +The core of the module is how it builds and injects a custom `CheckoutProcess` into Core's order flow. + +- **`OnePageCheckoutProcessProvider`**: implements Core's `CheckoutProcessProviderInterface`. This is the object the module returns from the hook. It exposes `isEnabled()` and `buildCheckoutProcess(CheckoutSession $session, TranslatorComponent $translator): CheckoutProcess`. +- **`OnePageCheckoutProcessBuilder`**: creates the `OnePageCheckoutProcess` instance, injecting a single unified step (`CheckoutOnePageStep`) instead of the native multi-step flow. +- **`OnePageCheckoutProcess`**: extends Core's `CheckoutProcess` and adds `isOnePageCheckoutEnabled()` to reflect the module's own availability check, keeping the `is_one_page_checkout_enabled` template variable consistent. +- **`OnePageCheckoutAvailability`**: the single source of truth for whether OPC is currently active. It reads the `PS_ONE_PAGE_CHECKOUT_ENABLED` configuration key (exposed on the module as `Ps_Onepagecheckout::CONFIG_ONE_PAGE_CHECKOUT_ENABLED`). + +The classes live under the module's `src/Checkout/` directory and use the `PrestaShop\Module\PsOnePageCheckout\Checkout` namespace. + +### AJAX endpoints + +Each checkout interaction is handled by a dedicated front controller under `controllers/front/`: guest initialization, address forms and persistence, country states, carrier listing and selection, payment listing and selection, cart totals, gift wrapping, draft persistence, and final submission. They all extend the abstract base class `Ps_OnepagecheckoutAbstractOpcJsonFrontController` and return structured JSON responses. + +{{% notice note %}} The set of endpoints grows with the module, so treat the [`controllers/front/` directory](https://github.com/PrestaShop/ps_onepagecheckout/tree/main/controllers/front) as the authoritative list rather than hardcoding endpoint names. The front-end never hardcodes these URLs either: the module exposes them at runtime through `window.ps_onepagecheckout.urls` (and to Smarty as `{$opc_urls}`). {{% /notice %}} + +### Form layer + +- **`OnePageCheckoutFormFactory`**: single factory that wires the checkout form and its data persister. Any code that needs the OPC form goes through this factory. +- **`OnePageCheckoutForm`**: main form object handling address, customer, and delivery data binding. +- **`OnePageCheckoutFormatter`**: prepares all template variables for the checkout view. +- **`BackOfficeConfigurationForm`**: handles the back-office configuration rendering and submission (Twig-based, targeting PrestaShop 9.2). + +### Front-end + +JavaScript is bundled with Webpack and organized around feature boundaries: + +- `opc-guest-init.js`: guest account creation flow +- `opc-address.js` / `opc-address-modal.js`: address form management +- `opc-carrier-list.js` / `opc-carrier-select.js`: shipping selection +- `opc-payment-list.js` / `opc-payment-select.js`: payment selection +- `opc-submit.js`: final submission and validation feedback +- `events.js`: the JS event contract (see below) + +The module also injects a runtime configuration block into the page from `hookActionFrontControllerSetMedia`, using `Media::addJsDef()` to expose all AJAX URLs and feature flags as a `window.ps_onepagecheckout` object. The front-end reads it through a small accessor (`views/js/runtime/opc-runtime.js`). This avoids hardcoded URLs in JavaScript and keeps the JS/PHP contract explicit. + +## The new hook: `actionCheckoutBuildProcess` + +This is the hook that lets `ps_onepagecheckout` override the native multi-step checkout process. + +### Purpose + +`actionCheckoutBuildProcess` is dispatched by Core **before** the native checkout process is built. A module implementing this hook returns an object implementing `CheckoutProcessProviderInterface`. If exactly one **enabled** provider is returned across all modules, Core uses its checkout process instead of the default multi-step flow. + +### The provider contract + +A module does not return a `CheckoutProcess` directly from the hook. It returns a **provider** implementing this interface: + +```php +// src/Adapter/Order/Checkout/CheckoutProcessProviderInterface.php + +namespace PrestaShop\PrestaShop\Adapter\Order\Checkout; + +use CheckoutProcess; +use CheckoutSession; +use PrestaShopBundle\Translation\TranslatorComponent; + +interface CheckoutProcessProviderInterface +{ + /** + * Indicates whether the module checkout can be used. + */ + public function isEnabled(): bool; + + /** + * Builds the checkout process for the current customer session. + */ + public function buildCheckoutProcess( + CheckoutSession $session, + TranslatorComponent $translator + ): CheckoutProcess; +} +``` + +The `CheckoutSession` and `TranslatorComponent` are handed to your provider's `buildCheckoutProcess()` method by Core, not passed as hook parameters. The hook itself is invoked with an empty parameter array. + +### How Core uses it + +The resolution logic lives in `CheckoutProcessProviderResolver` (`src/Adapter/Order/Checkout/CheckoutProcessProviderResolver.php`). + +Here is the actual code: + +```php +// src/Adapter/Order/Checkout/CheckoutProcessProviderResolver.php + +public function resolve(CheckoutSession $session, TranslatorComponent $translator): ?CheckoutProcess +{ + $provider = $this->resolveProvider(); + if ($provider === null) { + return null; // No single valid provider → use native checkout + } + + $checkoutProcess = $provider->buildCheckoutProcess($session, $translator); + + return $checkoutProcess instanceof CheckoutProcess ? $checkoutProcess : null; +} + +public function resolveProvider(): ?CheckoutProcessProviderInterface +{ + $providers = $this->getValidProviders(); + + // Exactly one enabled provider must be registered, otherwise fall back. + if (count($providers) !== 1) { + return null; + } + + return reset($providers); +} + +protected function getValidProviders(): array +{ + if ($this->cachedProviders !== null) { + return $this->cachedProviders; + } + + $providers = []; + // true → collect output from every module, keyed by module name. + $hookOutput = Hook::exec('actionCheckoutBuildProcess', [], null, true); + + if (!is_array($hookOutput)) { + return $this->cachedProviders = $providers; + } + + foreach ($hookOutput as $moduleName => $provider) { + if (!$provider instanceof CheckoutProcessProviderInterface || !$provider->isEnabled()) { + continue; + } + + $providers[$moduleName] = $provider; + } + + return $this->cachedProviders = $providers; +} +``` + +**Key design decisions:** + +1. **All modules are asked, exactly one enabled provider wins.** The hook is broadcast to every module. The resolver keeps only providers that are valid `CheckoutProcessProviderInterface` instances **and** report `isEnabled() === true`. If the count is not exactly one, Core falls back to native checkout. +2. **No registration config key.** A provider becomes active simply by returning an enabled provider from the hook: there is no configuration key to write on install. Conflicts between two enabled providers are resolved by falling back to native checkout, not by a "first wins" or "configured module" rule. +3. **Safe fallback by default.** If the hook output isn't an array, contains no valid enabled provider, or the provider's `buildCheckoutProcess()` returns something other than a `CheckoutProcess` instance, Core uses its own native checkout. There is no risk of a broken checkout from a module returning an unexpected value. + +### Where it is dispatched + +`OrderController::buildCheckoutProcess()` calls the resolver before constructing the native process: + +```php +// controllers/front/OrderController.php + +protected function buildCheckoutProcess(CheckoutSession $session, $translator) +{ + /** @var CheckoutProcessProviderResolver $checkoutProcessProviderResolver */ + $checkoutProcessProviderResolver = $this->get(CheckoutProcessProviderResolver::class); + $resolvedCheckoutProcess = $checkoutProcessProviderResolver->resolve($session, $translator); + if ($resolvedCheckoutProcess instanceof CheckoutProcess) { + return $resolvedCheckoutProcess; // Module-provided process wins + } + + // …otherwise build the native multi-step CheckoutProcess +} +``` + +### How ps_onepagecheckout implements it + +```php +// ps_onepagecheckout.php + +public function install() +{ + return $this->installInParent() + && $this->installOnePageCheckoutConfiguration() + && $this->registerHook('actionCheckoutBuildProcess') + && $this->registerHook('actionFrontControllerSetMedia') + // … other hooks + ; +} + +public function hookActionCheckoutBuildProcess(array $params = []): CheckoutProcessProviderInterface +{ + return new OnePageCheckoutProcessProvider($this->context, $this); +} +``` + +On install, the module registers the hook (and sets its `PS_ONE_PAGE_CHECKOUT_ENABLED` configuration). On uninstall, it clears that configuration. The provider it returns reports `isEnabled()` based on that same configuration key, so disabling OPC transparently restores native checkout. + +## Reacting to checkout updates + +One Page Checkout dynamically updates parts of the checkout page as the customer interacts with it. Depending on the action performed, sections such as carriers, payment methods, addresses, or the cart summary may be refreshed over AJAX without a full page reload. + +This affects **any** module that injects JavaScript widgets, buttons, hosted fields, iframes, or custom event listeners into those sections: make sure your code can be re-initialized after an update, not only on the initial page load. + +All updates are announced through the PrestaShop JavaScript event bus, so you react to them with `prestashop.on(...)`. The complete, authoritative list of events is defined in [`views/js/events.js`](https://github.com/PrestaShop/ps_onepagecheckout/blob/main/views/js/events.js) (`OPC_EVENTS`). The payment and carrier sections below show the events most relevant to each. + +## Payment modules compatibility + +Payment modules integrate with OPC through the standard Core `paymentOptions` hook: there is nothing OPC-specific you need to do to appear in the payment list. OPC gathers options through Core's `PaymentOptionsFinder`, so any module that already returns valid `PaymentOption` objects shows up automatically. See the [Payment modules]({{< relref "/9/modules/payment" >}}) documentation for how to build payment options. + +There are two integration styles, depending on how your payment is submitted. + +### Form-based payment options + +If your `PaymentOption` provides a `form` (or `action` and `inputs`), OPC renders it inside a `#pay-with-{id}-form` wrapper and submits it for you when the customer clicks **Place Order**. OPC first persists the checkout data (customer, address, delivery method) via its own AJAX submission, then submits your payment form. No extra code is required on your side: this is the default path. + +### Self-submitting (binary) payment options + +Payment modules that drive submission themselves, such as smart buttons, hosted fields, or redirect/iframe flows that set `binary = true`, are handled by the module itself. OPC does **not** try to submit an inner form for these: your module owns the submit and redirect. + +Because OPC still needs the checkout data (customer, selected address, and delivery method) persisted **before** your payment flow takes over, the module exposes a JavaScript entry point: + +```js +window.ps_onepagecheckout.submitBeforePayment(); +``` + +Call it from your payment flow **before** submitting or redirecting. It: + +- validates the checkout preconditions (a payment method is selected, the form is valid), +- persists the customer, address, and selected delivery method through OPC's submit endpoint, +- returns a `Promise`: resolve before continuing, and handle rejection (it rejects with `"OPC form not found."` if the OPC form isn't on the page), +- does **not** place the order or redirect: that remains your module's responsibility. + +Call it **once per order attempt**. + +OPC guards against concurrent submissions internally (an in-flight flag), so a second call made while the first is still running returns early without persisting anything. Trigger it from a single submit handler rather than from multiple event paths. + +```js +// Inside your payment module's submit handler (e.g. smart-button click) +try { + await window.ps_onepagecheckout.submitBeforePayment(); + // OPC has now saved customer / address / delivery method. + // Proceed with your own payment submission or redirect. + startMyPaymentFlow(); +} catch (error) { + // OPC form unavailable or preconditions not met: abort the payment. +} +``` + +### Re-initializing after a payment refresh + +OPC re-renders the payment methods section over AJAX, for example after a carrier or address change. If your payment option mounts widgets, hosted fields, or smart buttons, re-mount them whenever the section is refreshed by listening to `opcPaymentMethodsUpdated`: + +```js +prestashop.on('opcPaymentMethodsUpdated', () => { + remountMyPaymentWidgets(); +}); +``` + +## Carrier modules compatibility + +Carrier modules integrate with OPC through the standard Core carrier mechanism: there is nothing OPC-specific you need to do for your carrier to appear in the delivery options. See the [Carrier modules]({{< relref "/9/modules/carrier" >}}) documentation for how to build a carrier module. + +OPC loads and refreshes the carrier list over AJAX (via its `carriers` controller) when the selected delivery address changes, without a full page reload. If your carrier module injects UI into the delivery section, such as extra fields, pickup point pickers, or maps, re-initialize it after each refresh. + +The most relevant carrier events are: + +- `opcCarriersLoading`: the carrier list is being fetched. +- `opcCarriersUpdated`: the carrier list has been re-rendered (the server response includes the rendered HTML, updated cart totals, and the delivery address ID). +- `opcCarrierSelected`: the customer selected a carrier (the event carries the selected delivery option key). +- `opcCarriersFailed`: the carrier list failed to load. + +For example, re-mount your carrier UI whenever the list is refreshed: + +```js +prestashop.on('opcCarriersUpdated', () => { + remountMyCarrierWidgets(); +}); +``` + +## JavaScript event: `opcFinalSubmitStarted` + +If your module needs to react just before the final checkout submission, you can listen to the `opcFinalSubmitStarted` event emitted by `ps_onepagecheckout`. + +The event is emitted through PrestaShop's JavaScript event bus (`prestashop.emit` and `prestashop.on`), not as a native DOM `CustomEvent`: + +```js +// Emitted by ps_onepagecheckout +prestashop.emit('opcFinalSubmitStarted'); +``` + +To subscribe to the event: + +```js +prestashop.on('opcFinalSubmitStarted', () => { + // react just before the final checkout form is submitted +}); +``` + +Keep the following in mind: + +- The event is available only when One Page Checkout is enabled and the `ps_onepagecheckout` assets are loaded. +- The event must be consumed through the PrestaShop event bus, not through `document.addEventListener()`. +- No payload is provided with the event. + +The complete list of events emitted by the module is defined in [`views/js/events.js`](https://github.com/PrestaShop/ps_onepagecheckout/blob/main/views/js/events.js) (`OPC_EVENTS`). + +## If your module extends or injects into the OPC flow + +If your module adds steps, modifies templates, or injects data into the OPC checkout: + +- Check whether you were relying on Core classes or hooks that changed in 9.2 (much of the OPC-specific logic now lives in `ps_onepagecheckout`). +- The `PS_ONE_PAGE_CHECKOUT_ENABLED` configuration key is still **defined and read by Core** (via `OnePageCheckoutAvailabilityChecker` / `OnePageCheckoutSettings`), but the `ps_onepagecheckout` module now **provisions it** on install/uninstall and toggles it on enable/disable. Treat the module as the writer, and Core and the module as the readers. + +### If your module uses `actionFrontControllerSetMedia` or `actionFrontControllerSetVariables` + +Verify that your assets load after `ps_onepagecheckout` registers its scripts, or declare a dependency. + +### If your module reads `is_one_page_checkout_enabled` in templates + +This template variable is still available in the checkout template. It reflects the module's availability check (which in turn reads `PS_ONE_PAGE_CHECKOUT_ENABLED`). No action needed: the variable works the same way as before. + +## New behavior of the checkout process + +| Area | Previous behavior | 9.2 behavior | +| ----------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------- | +| OPC flow | Driven largely from Core (`OrderController`) | Provided by the `ps_onepagecheckout` module | +| Custom checkout injection | No standard hook, overrides required | `actionCheckoutBuildProcess` hook | +| Hook return value | N/A | Module returns a `CheckoutProcessProviderInterface`, not a `CheckoutProcess` directly | +| Provider selection | No standard mechanism | Resolver picks the single **enabled** provider, otherwise multiple or none means native fallback | +| `PS_ONE_PAGE_CHECKOUT_ENABLED` | Provisioned by Core | Provisioned by `ps_onepagecheckout` on install/uninstall, still read by Core | +| Checkout fallback | Native checkout always available | Native checkout used unless exactly one enabled provider is registered | + +## Compatibility + +These changes ship in **PrestaShop 9.2.0** and **`ps_onepagecheckout` 0.4.0**. The module declares compliance with PrestaShop `>= 9.2.0`. + +Modules targeting older PrestaShop versions are not affected. The `actionCheckoutBuildProcess` hook and `CheckoutProcessProviderInterface` do not exist in versions prior to 9.2, so guard your hook registration and any `instanceof` and type references accordingly if your module supports a version range. + +## See also + +- [One Page Checkout for theme developers]({{< relref "theme-developers" >}}): the template override paths, required DOM structure, Smarty variables, and JavaScript events your theme must respect. + +## Resources + +- [PR #41047: introduce the One Page Checkout hook](https://github.com/PrestaShop/PrestaShop/pull/41047) +- [`ps_onepagecheckout` module repository](https://github.com/PrestaShop/ps_onepagecheckout) +- [PrestaShop module developer documentation]({{< relref "/9/modules" >}}) diff --git a/modules/checkout/theme-developers.md b/modules/checkout/theme-developers.md new file mode 100644 index 0000000000..85d3cce62a --- /dev/null +++ b/modules/checkout/theme-developers.md @@ -0,0 +1,311 @@ +--- +title: One Page Checkout for theme developers +menuTitle: For theme developers +weight: 20 +--- + +# One Page Checkout for theme developers + +{{< minver v="9.2" title="true" >}} + +Starting with PrestaShop 9.2, the one-page checkout experience is delivered by the native **`ps_onepagecheckout`** module. If you maintain a theme, whether it is a fork of Classic, Hummingbird, or a fully custom theme, this page explains everything you need to know to keep your theme compatible. + +{{% notice info %}} **Who this page is for.** If you build or maintain a PrestaShop front-office theme and want it to work with `ps_onepagecheckout` when merchants enable it. {{% /notice %}} + +{{% notice note %}} This page documents the stable integration contract. For the exhaustive, always-current lists (selectors, events, controllers, runtime URLs), the [`ps_onepagecheckout` repository](https://github.com/PrestaShop/ps_onepagecheckout) is the source of truth, and the relevant files are linked from each section below. Where this page and the module's code differ, the code wins. {{% /notice %}} + +## How the module integrates with your theme + +`ps_onepagecheckout` uses the standard PrestaShop module template system. **The module ships its own complete set of front-office templates** and takes over checkout rendering by returning its template from the `hookDisplayOverrideTemplate` hook: + +```php +// ps_onepagecheckout.php +public function hookDisplayOverrideTemplate(array $params) +{ + if ( + $this->isOnePageCheckoutEnabled() + && $params['template_file'] === 'checkout/checkout' + && $params['controller'] instanceof OrderController + ) { + return 'module:ps_onepagecheckout/views/templates/front/checkout/checkout.tpl'; + } + + return null; +} +``` + +This means the checkout works **out of the box on any theme that was created from Hummingbird** or follows its template structure: you are not required to add any templates for OPC to function. The module's job is to: + +- Provide working default templates for the entire checkout. +- Override the core `checkout/checkout` template when OPC is enabled. +- Inject Smarty variables into the checkout template context. +- Load JavaScript bundles that handle AJAX interactions. +- Expose a runtime configuration object with AJAX endpoint URLs. +- Register a baseline front-office stylesheet (`one-page-checkout.css`). + +Your theme's job is to **optionally override** these templates and styles to match your design, and, when you do, to preserve the DOM contract (IDs, classes, `