Install the Recurring Orders feature

Edit on GitHub

This document describes how to install the Recurring Orders feature.

Install feature core

Follow the steps below to install the Recurring Orders feature core.

Prerequisites

To start feature integration, review and install the necessary features:

NAME VERSION INSTALLATION GUIDE
Spryker Core 202606.0 Install the Spryker Core feature
Company Account 202606.0 Install the Company Account feature
Checkout 202606.0 Install the Checkout feature
Purchasing Control 202606.0 Install the Purchasing Control feature
SSP Service Management 202606.0 Install the SSP Service Management feature
Optional features

Both Purchasing Control and SSP Service Management are optional:

  • Install Purchasing Control if you want buyers to assign a cost center and budget to a recurring order. Without it, the cost center and budget fields are not rendered on the recurring order forms.
  • Install SSP Service Management if your catalog contains service products. It restricts which service products buyers can add to a recurring schedule on the Review Required page. Without it, no service-specific restriction is applied.

1) Install the required modules

Required modules
composer require spryker-feature/order-experience-management:"^1.0.0" --update-with-dependencies
composer update \
  spryker/availability:"^9.32.0" \
  spryker/merchant:"^3.20.0" \
  spryker/merchant-product:"^1.3.0" \
  spryker/merchant-product-option:"^1.4.0" \
  spryker/merchant-storage:"^1.0.0" \
  spryker/merchant-switcher:"^0.6.8" \
  spryker/product-alternative-storage:"^1.15.0" \
  spryker/product-approval:"^1.5.0" \
  spryker/product-bundle:"^7.28.0" \
  spryker/product-cart-connector:"^4.15.0" \
  spryker/product-configuration-cart:"^1.1.0" \
  spryker/product-discontinued:"^1.15.0" \
  spryker/product-measurement-unit:"^5.0.0" \
  spryker/product-offer:"^1.18.0" \
  spryker/product-offer-storage:"^1.0.0" \
  spryker/product-packaging-unit:"^4.14.0" \
  spryker/product-packaging-unit-storage:"^5.5.0" \
  spryker/product-quantity:"^3.8.0" \
  spryker/shipment-type-storage:"^1.0.0" \
  spryker-feature/purchasing-control:"^1.1.1" \
  spryker-shop/checkout-page:"^3.42.0" \
  spryker-shop/customer-page:"^2.80.0" \
  spryker-shop/merchant-product-offer-widget:"^2.0.0" \
  spryker-shop/product-search-widget:"^3.9.0" \
  spryker-shop/shop-ui:"^2.0.0" \
  --with-dependencies
Modules required by the add-product flow

The Review Required page lets buyers add products to the order being placed. That flow pulls in the following modules, so install them even if your project does not use the corresponding functionality elsewhere:

MODULE PURPOSE
spryker-shop/product-search-widget Provides ProductConcreteSearchWidget and the products-list molecule that the add-product search bar reuses.
spryker-shop/merchant-product-offer-widget Backs the merchant offer selector rendered for an added product in a marketplace setup.
spryker-shop/shop-ui Provides the Component base class, AjaxProvider, and app registry used by the recurring order web components.
spryker/product-alternative-storage Provides the alternative products offered as substitutes for discontinued and substituted items.
spryker/shipment-type-storage Resolves the shipment types of an added product to decide whether a recurring order can serve it.
spryker/product-measurement-unit, spryker/product-packaging-unit-storage Detect products sold in measurement or packaging units so they can be excluded from the add-product picker.
Merchant Switcher

spryker/merchant-switcher is not a dependency of the feature. Include it in the composer update command only if your project uses Merchant Switcher. Its checkout error type is one of the types mapped to a review reason group, so a project without the module simply never produces that error.

2) Set up database schema and transfer objects

Apply database changes and generate entity and transfer changes:

console propel:install
console transfer:generate
Verification

Make sure the following changes have been applied in the database:

DATABASE ENTITY TYPE EVENT
spy_recurring_schedule table created
spy_recurring_schedule_item table created
spy_recurring_schedule_history table created
spy_recurring_schedule_forecast table created

Make sure the following changes have been applied in transfer objects:

TRANSFER TYPE EVENT PATH
RecurringSchedule class created src/Generated/Shared/Transfer/RecurringScheduleTransfer.php
RecurringScheduleCollection class created src/Generated/Shared/Transfer/RecurringScheduleCollectionTransfer.php
RecurringScheduleCriteria class created src/Generated/Shared/Transfer/RecurringScheduleCriteriaTransfer.php
RecurringScheduleConditions class created src/Generated/Shared/Transfer/RecurringScheduleConditionsTransfer.php
RecurringScheduleCollectionRequest class created src/Generated/Shared/Transfer/RecurringScheduleCollectionRequestTransfer.php
RecurringScheduleCollectionResponse class created src/Generated/Shared/Transfer/RecurringScheduleCollectionResponseTransfer.php
RecurringScheduleItem class created src/Generated/Shared/Transfer/RecurringScheduleItemTransfer.php
RecurringScheduleItemAddition class created src/Generated/Shared/Transfer/RecurringScheduleItemAdditionTransfer.php
RecurringScheduleHistory class created src/Generated/Shared/Transfer/RecurringScheduleHistoryTransfer.php
RecurringScheduleValidationResult class created src/Generated/Shared/Transfer/RecurringScheduleValidationResultTransfer.php
RecurringScheduleItemReview class created src/Generated/Shared/Transfer/RecurringScheduleItemReviewTransfer.php
RecurringScheduleSubstituteOption class created src/Generated/Shared/Transfer/RecurringScheduleSubstituteOptionTransfer.php
RecurringScheduleShippingAddressChoice class created src/Generated/Shared/Transfer/RecurringScheduleShippingAddressChoiceTransfer.php
RecurringScheduleReviewResponse class created src/Generated/Shared/Transfer/RecurringScheduleReviewResponseTransfer.php
RecurringScheduleEventRequest class created src/Generated/Shared/Transfer/RecurringScheduleEventRequestTransfer.php
RecurringScheduleEventResponse class created src/Generated/Shared/Transfer/RecurringScheduleEventResponseTransfer.php
RecurringScheduleStatusCount class created src/Generated/Shared/Transfer/RecurringScheduleStatusCountTransfer.php
RecurringScheduleStatusCountCollection class created src/Generated/Shared/Transfer/RecurringScheduleStatusCountCollectionTransfer.php
RecurringScheduleForecast class created src/Generated/Shared/Transfer/RecurringScheduleForecastTransfer.php
RecurringScheduleForecastCollection class created src/Generated/Shared/Transfer/RecurringScheduleForecastCollectionTransfer.php
RecurringScheduleForecastSnapshot class created src/Generated/Shared/Transfer/RecurringScheduleForecastSnapshotTransfer.php
RecurringScheduleTableFilter class created src/Generated/Shared/Transfer/RecurringScheduleTableFilterTransfer.php
RecurringScheduleDueData class created src/Generated/Shared/Transfer/RecurringScheduleDueDataTransfer.php
RecurringScheduleError class created src/Generated/Shared/Transfer/RecurringScheduleErrorTransfer.php
RecurringOrderSettings class created src/Generated/Shared/Transfer/RecurringOrderSettingsTransfer.php
RecurringOrderQuoteUpdateRequest class created src/Generated/Shared/Transfer/RecurringOrderQuoteUpdateRequestTransfer.php
RecurringOrderQuoteUpdateResponse class created src/Generated/Shared/Transfer/RecurringOrderQuoteUpdateResponseTransfer.php
RecurringOrderReloadResult class created src/Generated/Shared/Transfer/RecurringOrderReloadResultTransfer.php
Quote.recurringOrderSettings property created src/Generated/Shared/Transfer/QuoteTransfer.php

3) Set up data import

Import the CMS blocks that provide the HTML and text templates for recurring order notification emails.

The CMS block definitions are provided in the module at src/SprykerFeature/OrderExperienceManagement/data/import/cms_block.csv. Copy the contents of that file and add them to data/import/common/common/cms_block.csv.

For each store you want to enable the email notifications in, add the corresponding block keys to data/import/common/{store}/cms_block_store.csv.

Import the data:

console data:import:cms-block
console data:import:cms-block-store
Verification

In the Back Office, under Content > Blocks, make sure the CMS blocks from the module file are present and active.

4) Set up behavior

Enable the following behaviors by registering the plugins.

Set up Checkout plugins

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
RecurringOrderCheckoutPreConditionPlugin Validates the quote is eligible for a recurring order before checkout proceeds. Checks that the quote is not locked, not from an RFQ, not a guest session, the payment method is invoice-based, and the cadence type is registered and valid. Also runs the registered recurring order checkout validator plugins. Does nothing when recurringOrderSettings is not set on the quote. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout
RecurringOrdersCheckoutPostSavePlugin Creates a recurring schedule and registers it with the state machine after the order is successfully saved. Does nothing when recurringOrderSettings is not set on the quote. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout

src/Pyz/Zed/Checkout/CheckoutDependencyProvider.php

<?php

namespace Pyz\Zed\Checkout;

use Spryker\Zed\Checkout\CheckoutDependencyProvider as SprykerCheckoutDependencyProvider;
use Spryker\Zed\Kernel\Container;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout\RecurringOrderCheckoutPreConditionPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout\RecurringOrdersCheckoutPostSavePlugin;

class CheckoutDependencyProvider extends SprykerCheckoutDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list<\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface>
     */
    protected function getCheckoutPreConditions(Container $container): array
    {
        return [
            // ...
            new RecurringOrderCheckoutPreConditionPlugin(), #RecurringOrdersFeature
        ];
    }

    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list<\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPostSaveInterface>
     */
    protected function getCheckoutPostHooks(Container $container): array
    {
        return [
            // ...
            new RecurringOrdersCheckoutPostSavePlugin(), #RecurringOrdersFeature
        ];
    }
}
Verification
  1. Add a product to the cart, set a recurring order cadence on the quote, and complete checkout. Make sure a recurring schedule is created in spy_recurring_schedule.
  2. Attempt to place a recurring order with a non-invoice payment method. Make sure checkout is blocked.
  3. Attempt to place a recurring order with an invalid cadence type. Make sure checkout is blocked.

Set up cadence type and schedule validator plugins

Register the built-in cadence type and schedule validator plugins:

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
WeeklyCadenceTypePlugin Calculates the next trigger date 7 days after the current trigger date. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence
BiWeeklyCadenceTypePlugin Calculates the next trigger date 14 days after the current trigger date. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence
MonthlyCadenceTypePlugin Calculates the next trigger date on the same day of the following month. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence
EveryNWeeksCadenceTypePlugin Calculates the next trigger date every N weeks. Requires cadenceValue to be set on the schedule. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence
PriceScheduleValidatorPlugin Detects price increases on recurring schedule items compared to their stored reference prices before order placement. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator
CheckoutPlaceabilityScheduleValidatorPlugin Simulates a checkout to detect availability, discontinuation, or product approval issues on recurring schedule items before order placement. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator

src/Pyz/Zed/OrderExperienceManagement/OrderExperienceManagementDependencyProvider.php

<?php

namespace Pyz\Zed\OrderExperienceManagement;

use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\BiWeeklyCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\EveryNWeeksCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\MonthlyCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\WeeklyCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator\CheckoutPlaceabilityScheduleValidatorPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator\PriceScheduleValidatorPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\OrderExperienceManagementDependencyProvider as SprykerOrderExperienceManagementDependencyProvider;

class OrderExperienceManagementDependencyProvider extends SprykerOrderExperienceManagementDependencyProvider
{
    /**
     * @return array<\SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\CadenceTypePluginInterface>
     */
    protected function getCadenceTypePlugins(): array
    {
        return [
            new WeeklyCadenceTypePlugin(), #RecurringOrdersFeature
            new BiWeeklyCadenceTypePlugin(), #RecurringOrdersFeature
            new MonthlyCadenceTypePlugin(), #RecurringOrdersFeature
            new EveryNWeeksCadenceTypePlugin(), #RecurringOrdersFeature
        ];
    }

    /**
     * @return array<\SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\ScheduleValidatorPluginInterface>
     */
    protected function getScheduleValidatorPlugins(): array
    {
        return [
            new PriceScheduleValidatorPlugin(), #RecurringOrdersFeature
            new CheckoutPlaceabilityScheduleValidatorPlugin(), #RecurringOrdersFeature
        ];
    }
}
Verification

Make sure all four cadence types (weekly, bi-weekly, monthly, every N weeks) are available when setting up a recurring order on the storefront.

Set up recurring order checkout validator plugins

Recurring order checkout validator plugins reject a quote that must not be checked out as a recurring order. RecurringOrderCheckoutPreConditionPlugin runs them after its own eligibility checks, and only when QuoteTransfer.recurringOrderSettings is set—a regular checkout is never affected.

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
BudgetApprovalRuleRecurringOrderCheckoutValidatorPlugin Blocks checkout when the budget selected on the quote uses the require_approval enforcement rule. A recurring order places its follow-up orders unattended, so no approval can be granted for them upfront. Applies regardless of the quote grand total and the remaining budget amount. Passes when no budget is selected or the budget uses another enforcement rule. Purchasing Control feature SprykerFeature\Zed\PurchasingControl\Communication\Plugin\OrderExperienceManagement

src/Pyz/Zed/OrderExperienceManagement/OrderExperienceManagementDependencyProvider.php

<?php

namespace Pyz\Zed\OrderExperienceManagement;

use SprykerFeature\Zed\OrderExperienceManagement\OrderExperienceManagementDependencyProvider as SprykerOrderExperienceManagementDependencyProvider;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\OrderExperienceManagement\BudgetApprovalRuleRecurringOrderCheckoutValidatorPlugin;

class OrderExperienceManagementDependencyProvider extends SprykerOrderExperienceManagementDependencyProvider
{
    /**
     * @return array<\SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\RecurringOrderCheckoutValidatorPluginInterface>
     */
    protected function getRecurringOrderCheckoutValidatorPlugins(): array
    {
        return [
            new BudgetApprovalRuleRecurringOrderCheckoutValidatorPlugin(), #RecurringOrdersFeature
        ];
    }
}
Custom checkout validators

To block a recurring order checkout for a project-specific reason, implement SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\RecurringOrderCheckoutValidatorPluginInterface. Return a CheckoutErrorTransfer whose message is your own glossary key—and whose parameters are set when the message is parameterized—to reject the quote, or null when your check passes.

Use this extension point for rules that apply to the recurring order as a whole at checkout time. To reject an individual product a buyer adds later on the Review Required page, use AddedItemValidatorPluginInterface instead.

Verification

Set a budget’s enforcement rule to Require approval in the Back Office, assign it to a cart, and set up that cart as a recurring order at checkout. Make sure checkout is blocked with a message stating that the selected budget requires approval and cannot be used for a recurring order.

Set up added item validator plugins

Added item validator plugins reject a product the buyer added on the Review Required page. They run after the item is resolved through the cart, so they see the resolved price, shipment, and any data the cart item expanders added. They also run after the module’s own availability, price, and shipment checks, so they are only reached when those pass.

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
ServiceProductAddedItemValidatorPlugin Rejects a service item whose resolved shipment method is not one a recurring order can serve. A service fulfilled on site or in a service center needs an appointment, which a recurring order places unattended and therefore cannot book. SSP Service Management feature SprykerFeature\Zed\SelfServicePortal\Communication\Plugin\OrderExperienceManagement

src/Pyz/Zed/OrderExperienceManagement/OrderExperienceManagementDependencyProvider.php

<?php

namespace Pyz\Zed\OrderExperienceManagement;

use SprykerFeature\Zed\OrderExperienceManagement\OrderExperienceManagementDependencyProvider as SprykerOrderExperienceManagementDependencyProvider;
use SprykerFeature\Zed\SelfServicePortal\Communication\Plugin\OrderExperienceManagement\ServiceProductAddedItemValidatorPlugin;

class OrderExperienceManagementDependencyProvider extends SprykerOrderExperienceManagementDependencyProvider
{
    /**
     * @return array<\SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\AddedItemValidatorPluginInterface>
     */
    protected function getAddedItemValidatorPlugins(): array
    {
        return [
            new ServiceProductAddedItemValidatorPlugin(), #RecurringOrdersFeature
        ];
    }
}

ServiceProductAddedItemValidatorPlugin reads the accepted shipment type keys from SelfServicePortalConfig. The module default is an empty list, which accepts service products with any shipment type. Define the shipment types your project can serve unattended by overriding the constant in the shared config, which applies it in Yves and Zed at once:

src/Pyz/Shared/SelfServicePortal/SelfServicePortalConfig.php

<?php

namespace Pyz\Shared\SelfServicePortal;

use SprykerFeature\Shared\SelfServicePortal\SelfServicePortalConfig as SprykerSelfServicePortalConfig;

class SelfServicePortalConfig extends SprykerSelfServicePortalConfig
{
    /**
     * @uses \Spryker\Shared\ShipmentType\ShipmentTypeConfig::SHIPMENT_TYPE_DELIVERY
     *
     * @var string
     */
    public const SHIPMENT_TYPE_DELIVERY = 'delivery';

    /**
     * @var array<string>
     */
    public const RECURRING_ORDER_SERVICE_SHIPMENT_TYPE_KEYS = [
        self::SHIPMENT_TYPE_DELIVERY,
    ];
}
CONFIGURATION CONSTANT DEFAULT DESCRIPTION
RECURRING_ORDER_SERVICE_SHIPMENT_TYPE_KEYS [] Shipment type keys a service product must support to be part of a recurring order. An empty list accepts service products with any shipment type. getRecurringOrderServiceShipmentTypeKeys() returns this constant, so overriding the constant is enough.
Configure the constant once

If your project follows Install the SSP Service Management feature, the constant is already defined in src/Pyz/Shared/SelfServicePortal/SelfServicePortalConfig.php. Define it in one place only.

Custom added item validators

To reject added products for a project-specific reason, implement SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\AddedItemValidatorPluginInterface. Return an ErrorTransfer carrying your own glossary key to reject the addition, or null to accept it. Pair it with an AddedProductConcreteRestrictionPluginInterface implementation in Yves so the product is hidden from the picker as well as rejected on approval.

Verification

Open the Review Required page for a schedule and add a service product whose shipment type is not in RECURRING_ORDER_SERVICE_SHIPMENT_TYPE_KEYS. Make sure the approval is rejected with an error message naming the product SKU.

Set up the state machine handler

Register the recurring orders state machine handler:

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
RecurringOrdersStateMachineHandlerPlugin Registers the RecurringOrder state machine process, maps commands and conditions to plugins, updates the state machine item state on each transition, and returns schedule items by state IDs. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\StateMachine

src/Pyz/Zed/StateMachine/StateMachineDependencyProvider.php

<?php

namespace Pyz\Zed\StateMachine;

use Spryker\Zed\StateMachine\StateMachineDependencyProvider as SprykerStateMachineDependencyProvider;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\StateMachine\RecurringOrdersStateMachineHandlerPlugin;

class StateMachineDependencyProvider extends SprykerStateMachineDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\StateMachine\Dependency\Plugin\StateMachineHandlerInterface>
     */
    protected function getStateMachineHandlers(): array
    {
        return [
            // ...
            new RecurringOrdersStateMachineHandlerPlugin(), #RecurringOrdersFeature
        ];
    }
}

Copy the state machine process XML from the module into your project. The example file is located at src/SprykerFeature/OrderExperienceManagement/config/Zed/StateMachine/RecurringOrder/RecurringOrderStateMachine.xml in the module. Add it to your project at the following path:

config/Zed/StateMachine/RecurringOrder/RecurringOrderStateMachine.xml

The handler plugin registers the following commands and conditions, so you do not need to register them individually. Reference them by name if you extend the process XML:

TYPE NAME SPECIFICATION
Condition RecurringOrders/IsScheduleDue Checks whether the schedule has entered its notification window.
Condition RecurringOrders/IsPlacementDue Checks whether the trigger date has been reached.
Condition RecurringOrders/IsScheduleValid Runs the registered schedule validator plugins and reports whether the schedule can be placed.
Condition RecurringOrders/IsOrderPlaced Checks whether the sales order has been created successfully.
Command RecurringOrders/NotifyBuyer Sends the pre-trigger notification email.
Command RecurringOrders/PlaceOrder Places the order from the stored quote data.
Command RecurringOrders/CompletePlacement Writes the placement result to the schedule history.
Command RecurringOrders/AdvanceSchedule Calculates and stores the next trigger date using the registered cadence type plugin.

The process XML resolves the confirm event out of review_required into two transitions, so the target depends on when the buyer approves the review:

<transition condition="RecurringOrders/IsPlacementDue">
    <source>review_required</source>
    <target>confirmed</target>
    <event>confirm</event>
</transition>

<transition>
    <source>review_required</source>
    <target>active</target>
    <event>confirm</event>
</transition>
  • The buyer approves on or after the trigger date: RecurringOrders/IsPlacementDue evaluates to true, the schedule moves to confirmed, and the order is placed in this cycle.
  • The buyer approves before the trigger date: the condition evaluates to false, and the schedule returns to active. Nothing is placed yet, and the schedule waits for its trigger date.
Transition order

The conditional transition must precede the unconditional one. The state machine evaluates transitions in document order and takes the first match, so an unconditional transition placed first makes every confirm event resolve to active and no order is ever placed. Keep this order if you extend the process XML.

Verification
  1. In the Back Office, under Maintenance > State Machine, make sure the RecurringOrderStateMachine process is listed and the diagram renders correctly.
  2. Approve a review for a schedule whose trigger date is still in the future. Make sure the schedule status returns to Active and no order is placed.
  3. Approve a review for a schedule whose trigger date has been reached. Make sure the schedule status becomes Confirmed and the order is placed.

Set up Mail plugins

Register the following mail type builder plugins:

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
RecurringOrderUpcomingNotificationMailTypeBuilderPlugin Builds the pre-trigger notification email sent to the buyer a configurable number of hours before the scheduled order is placed. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail
RecurringOrderValidationFailedMailTypeBuilderPlugin Builds the review-required notification email sent to the buyer when a price increase or product availability issue is detected. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail
RecurringOrderFailureMailTypeBuilderPlugin Builds the order placement failure notification email sent to the buyer when order placement fails. None SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail

src/Pyz/Zed/Mail/MailDependencyProvider.php

<?php

namespace Pyz\Zed\Mail;

use Spryker\Zed\Mail\MailDependencyProvider as SprykerMailDependencyProvider;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail\RecurringOrderFailureMailTypeBuilderPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail\RecurringOrderUpcomingNotificationMailTypeBuilderPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail\RecurringOrderValidationFailedMailTypeBuilderPlugin;

class MailDependencyProvider extends SprykerMailDependencyProvider
{
    /**
     * @return array<\Spryker\Zed\MailExtension\Dependency\Plugin\MailTypeBuilderPluginInterface>
     */
    protected function getMailTypeBuilderPlugins(): array
    {
        return [
            // ...
            new RecurringOrderUpcomingNotificationMailTypeBuilderPlugin(), #RecurringOrdersFeature
            new RecurringOrderValidationFailedMailTypeBuilderPlugin(), #RecurringOrdersFeature
            new RecurringOrderFailureMailTypeBuilderPlugin(), #RecurringOrdersFeature
        ];
    }
}
Verification

Trigger a recurring order cycle and verify the following:

  • A pre-trigger notification email is sent to the buyer within the configured notification window hours.
  • When a price increase or product availability issue is detected, a review-required email is sent.
  • When order placement fails, a failure notification email is sent.

Set up permissions

By default, a company user can only see their own recurring orders. To allow users to view recurring orders across their company or business unit, register the following permission plugins:

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
SeeCompanyOrdersPermissionPlugin Grants permission to view all recurring orders within the company. Assign to company roles that should have company-wide visibility. None Spryker\Zed\CompanySalesConnector\Communication\Plugin\Permission
SeeBusinessUnitOrdersPermissionPlugin Grants permission to view all recurring orders within the company business unit. Assign to company roles that should have business-unit-wide visibility. None Spryker\Zed\CompanyBusinessUnitSalesConnector\Communication\Plugin\Permission

src/Pyz/Zed/Permission/PermissionDependencyProvider.php

<?php

namespace Pyz\Zed\Permission;

use Spryker\Zed\CompanyBusinessUnitSalesConnector\Communication\Plugin\Permission\SeeBusinessUnitOrdersPermissionPlugin;
use Spryker\Zed\CompanySalesConnector\Communication\Plugin\Permission\SeeCompanyOrdersPermissionPlugin;
use Spryker\Zed\Permission\PermissionDependencyProvider as SprykerPermissionDependencyProvider;

class PermissionDependencyProvider extends SprykerPermissionDependencyProvider
{
    /**
     * @return array<\Spryker\Shared\PermissionExtension\Dependency\Plugin\PermissionPluginInterface>
     */
    protected function getPermissionPlugins(): array
    {
        return [
            // ...
            new SeeCompanyOrdersPermissionPlugin(), #RecurringOrdersFeature
            new SeeBusinessUnitOrdersPermissionPlugin(), #RecurringOrdersFeature
        ];
    }
}

src/Pyz/Client/Permission/PermissionDependencyProvider.php

<?php

namespace Pyz\Client\Permission;

use Spryker\Client\CompanyBusinessUnitSalesConnector\Plugin\Permission\SeeBusinessUnitOrdersPermissionPlugin;
use Spryker\Client\CompanySalesConnector\Plugin\Permission\SeeCompanyOrdersPermissionPlugin;
use Spryker\Client\Permission\PermissionDependencyProvider as SprykerPermissionDependencyProvider;

class PermissionDependencyProvider extends SprykerPermissionDependencyProvider
{
    /**
     * @return array<\Spryker\Shared\PermissionExtension\Dependency\Plugin\PermissionPluginInterface>
     */
    protected function getPermissionPlugins(): array
    {
        return [
            // ...
            new SeeCompanyOrdersPermissionPlugin(), #RecurringOrdersFeature
            new SeeBusinessUnitOrdersPermissionPlugin(), #RecurringOrdersFeature
        ];
    }
}
Verification

In the Back Office, under Customers > Company Roles, assign SeeCompanyOrdersPermissionPlugin to a company role. Make sure company users with that role can see all recurring orders within their company on the storefront.

Assign SeeBusinessUnitOrdersPermissionPlugin to a role. Make sure users with that role can see all recurring orders within their business unit.

Set up console commands

Register the following console commands:

COMMAND CONSOLE CLASS SPECIFICATION NAMESPACE
recurring-orders:forecast:refresh RecurringOrderForecastRefreshConsole Recalculates the monthly recurring volume forecast—orders already placed plus schedules still due—and stores it as a snapshot in spy_recurring_schedule_forecast. The Back Office Recurring Order Schedules page reads the stored snapshot instead of recalculating on each request. SprykerFeature\Zed\OrderExperienceManagement\Communication\Console
recurring-orders:trigger-placement RecurringOrderTriggerConsole Manually triggers order placement for a specific recurring schedule. Optional—intended for development and debugging. SprykerFeature\Zed\OrderExperienceManagement\Communication\Console

src/Pyz/Zed/Console/ConsoleDependencyProvider.php

<?php

namespace Pyz\Zed\Console;

use Spryker\Zed\Console\ConsoleDependencyProvider as SprykerConsoleDependencyProvider;
use Spryker\Zed\Kernel\Container;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Console\RecurringOrderForecastRefreshConsole;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Console\RecurringOrderTriggerConsole;

class ConsoleDependencyProvider extends SprykerConsoleDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list<\Symfony\Component\Console\Command\Command>
     */
    protected function getConsoleCommands(Container $container): array
    {
        return [
            // ...
            new RecurringOrderForecastRefreshConsole(), #RecurringOrdersFeature
            new RecurringOrderTriggerConsole(), #RecurringOrdersFeature
        ];
    }
}

RecurringOrderTriggerConsole runs the same placement logic as the state machine PlaceOrder command, which makes it useful for development, debugging, and one-off operational tasks:

# Trigger placement by numeric ID
console recurring-orders:trigger-placement 42

# Trigger placement by UUID
console recurring-orders:trigger-placement 550e8400-e29b-41d4-a716-446655440000

# Run pre-placement validation first; aborts if validation fails
console recurring-orders:trigger-placement 42 --validate

The --validate flag runs all registered ScheduleValidatorPlugin implementations (for example, PriceScheduleValidatorPlugin and CheckoutPlaceabilityScheduleValidatorPlugin) before attempting placement. If any validator reports a failure, the command exits with an error and does not place the order.

Development use only

recurring-orders:trigger-placement is intended for development and debugging. Do not use it in production automated pipelines—the state machine cron job is the intended trigger for production order placement.

Verification

Run console recurring-orders:forecast:refresh. Make sure the command completes successfully and a row with forecast_key set to monthly is created in spy_recurring_schedule_forecast.

Set up cron jobs

The recurring orders state machine relies on a cron job to evaluate condition transitions, and the Back Office forecast relies on a cron job to refresh its snapshot. Register the jobs in your Jenkins configuration:

config/Zed/cronjobs/jenkins.php

/* RecurringOrder StateMachine */
$jobs[] = [
    'name' => 'recurring-order-check-conditions',
    'command' => '$PHP_BIN vendor/bin/console state-machine:check-condition RecurringOrder',
    'schedule' => '* * * * *',
    'enable' => true,
];

/* RecurringOrder forecast */
$jobs[] = [
    'name' => 'recurring-order-forecast-refresh',
    'command' => '$PHP_BIN vendor/bin/console recurring-orders:forecast:refresh',
    'schedule' => '0 * * * *',
    'enable' => true,
];
Scheduling recommendation

To have recurring orders placed before the business day starts, set trigger dates to an early morning time (for example, 01:00) and configure the schedule grace period to 18 hours or more. With an 18-hour window, the pre-trigger notification is sent the previous afternoon (after 12:00), allowing the buyer to review or skip before the order is placed overnight.

If your project uses Symfony Scheduler instead of Jenkins, register the equivalent jobs in your scheduler config:

src/Pyz/Zed/SymfonyScheduler/SymfonySchedulerConfig.php

'recurring-orders-check-condition' => [
    'command' => '$PHP_BIN vendor/bin/console state-machine:check-condition RecurringOrder',
    'schedule' => '* * * * *',
],
'recurring-orders-clear-locks' => [
    'command' => '$PHP_BIN vendor/bin/console state-machine:clear-locks',
    'schedule' => '0 6 * * *',
],
'recurring-orders-forecast-refresh' => [
    'command' => '$PHP_BIN vendor/bin/console recurring-orders:forecast:refresh',
    'schedule' => '0 * * * *',
],
Verification
  1. Activate a recurring schedule. Make sure the state machine condition check job runs and the schedule transitions from draft to active within one minute.
  2. Make sure the forecast refresh job runs hourly and updates calculated_at in spy_recurring_schedule_forecast.

Set up the Back Office navigation

To make the recurring order schedules list available in the Back Office, add the navigation entry to the <pages> element of the <sales> node in your project navigation:

config/Zed/navigation.xml

<sales>
    <!-- ... -->
    <pages>
        <!-- ... -->
        <recurring-order-schedules>
            <label>Recurring Order Schedules</label>
            <title>Recurring Order Schedules</title>
            <bundle>order-experience-management</bundle>
            <controller>recurring-schedule</controller>
            <action>index</action>
            <visible>1</visible>
        </recurring-order-schedules>
    </pages>
</sales>

Rebuild the navigation cache:

console navigation:build-cache
Verification

In the Back Office, go to Sales > Recurring Order Schedules. Make sure the list of recurring order schedules is displayed with the status and cadence filters, and that the Total Committed Recurring Volume widget shows the already-placed and still-planned breakdown. Open a schedule and make sure the detail view renders the schedule items with their merchant and configurable bundle names, the customer, and a link to the source order.

Configure product bundle field copying

Product Bundles feature

This step is only required if your project uses the Product Bundles feature.

Override the allowed fields to copy so that shipment information is preserved when recurring orders re-create bundle items:

CONFIGURATION METHOD SPECIFICATION PREREQUISITES NAMESPACE
getAllowedBundleItemFieldsToCopy() Returns the list of ItemTransfer fields copied from bundle items when they are duplicated during order re-creation. Adding ItemTransfer::SHIPMENT ensures the shipment is preserved on each recurring order placement. None Pyz\Zed\ProductBundle

src/Pyz/Zed/ProductBundle/ProductBundleConfig.php

<?php

namespace Pyz\Zed\ProductBundle;

use Generated\Shared\Transfer\ItemTransfer;
use Spryker\Zed\ProductBundle\ProductBundleConfig as SprykerProductBundleConfig;

class ProductBundleConfig extends SprykerProductBundleConfig
{
    /**
     * @return list<string>
     */
    public function getAllowedBundleItemFieldsToCopy(): array
    {
        return [
            ItemTransfer::SHIPMENT,
        ];
    }
}
Verification

Add a product bundle to the cart, set up a recurring order, and complete checkout. On the next scheduled order placement, make sure the bundle items are re-created with the correct shipment assignment.

5) Configure module behavior

Override the following configuration methods in your project (if needed) to adjust the default behavior:

src/Pyz/Zed/OrderExperienceManagement/OrderExperienceManagementConfig.php

<?php

namespace Pyz\Zed\OrderExperienceManagement;

use SprykerFeature\Zed\OrderExperienceManagement\OrderExperienceManagementConfig as SprykerOrderExperienceManagementConfig;

class OrderExperienceManagementConfig extends SprykerOrderExperienceManagementConfig
{
    /**
     * Specification:
     * - Returns the number of hours before the trigger date when the pre-trigger notification is sent.
     * - Default: 48 hours.
     * - Overriding this value affects all schedules that do not have a per-schedule override.
     *
     * @api
     */
    public function getDefaultNotificationWindowHours(): int
    {
        return 18;
    }

    /**
     * Specification:
     * - Returns the relative date modifier used as the forecast period's lower bound.
     * - The bound applies to both forecast inputs: schedules still due to run and orders already placed.
     * - Use FORECAST_PERIOD_FROM_TODAY to cover only the remainder of the current month.
     *
     * @api
     */
    public function getForecastPeriodFrom(): string
    {
        return static::FORECAST_PERIOD_FROM_TODAY;
    }
}
CONFIGURATION METHOD DEFAULT DESCRIPTION
getDefaultNotificationWindowHours() 48 Number of hours before the trigger date when the pre-trigger notification is sent. Per-schedule overrides stored in spy_recurring_schedule.notification_window_hours take precedence.
getReviewReasonGroupMap() out_of_stock, unavailable, and discontinued, each mapped to the checkout error types that resolve to it Maps review reason groups to checkout error types. The module maps every checkout error type it ships with, so extend it only to map project-specific error types or to introduce a new group.
getNonPurchasableReviewReasonGroups() Every key of getReviewReasonGroupMap(): out_of_stock, unavailable, discontinued Review reason groups whose items block order placement and must be removed before the order can proceed. All groups block placement by default. Narrow the list if items of a group should stay purchasable.
getDefaultReviewReasonGroup() REVIEW_REASON_GROUP_UNAVAILABLE Fallback review reason group used when a checkout error type does not match any known group.
getForecastPeriodFrom() FORECAST_PERIOD_FROM_MONTH_START Lower bound of the Back Office forecast period. Applies to both forecast inputs: schedules still due to run and orders already placed. Switch to FORECAST_PERIOD_FROM_TODAY to cover only the remainder of the current month.
getForecastPeriodTo() FORECAST_PERIOD_TO_MONTH_END Upper bound of the Back Office forecast period. Applies to both forecast inputs.
getMonthlyForecastKey() monthly Key under which the forecast snapshot is stored in spy_recurring_schedule_forecast.
getBackOfficeFilterStatuses() Active, Paused, Review required, Cancelled, Failed Statuses selectable in the Back Office Recurring Order Schedules filter.
getBackOfficeFilterCadenceTypes() Weekly, Bi-weekly, Monthly, Every N weeks Cadence types selectable in the Back Office Recurring Order Schedules filter. Extend it when you register a custom cadence type plugin.
getSmStateNameToStatusMap() See OrderExperienceManagementConfig Maps each state machine state name to the public schedule status it represents. State names that are not in the map resolve to no status. Extend it when you add states to the process XML.
getSupportedAddedItemShipmentTypeKeys() [delivery, on-site-service] Shipment type keys accepted for products added on the Review Required page, in preference order. When an offer or store exposes several supported types, the first one is used.
getSubstitutableReviewReasons() [discontinued, substituted] Review reason groups for which a substitute product can be offered on the Review Required page.
getPriceChangeReviewReasons() [price_increased] Review reason groups counted as price changes in the Review Required summary.
getUnavailableReviewReasons() [unavailable, out_of_stock] Review reason groups counted as unavailable items in the Review Required summary.
isMeasurementUnitProductAdditionRestricted() true Defines whether products sold in measurement units can be added on the Review Required page. The picker offers no sales unit selector, so a typed quantity would silently mean N times the store default sales unit instead of N base units.
isPackagingUnitProductAdditionRestricted() true Defines whether products sold in packaging units can be added on the Review Required page. The picker offers no amount input, so the resolved item would carry no amount, stay unsplit, and reserve no stock for the lead product.
getStateMachineName() RecurringOrder Name of the state machine. Must match the directory name under config/Zed/StateMachine.
getProcessName() RecurringOrderStateMachine Name of the state machine process definition file, without the .xml extension.
getInitialState() draft Initial state assigned to a newly created schedule.
getBaseUrlYves() SprykerConstants::BASE_URL_YVES Base storefront URL used to build the links included in notification emails.
getRecurringOrderDetailUrlPath() /recurring-orders Storefront path used to build the schedule detail link in notification emails. Update it if you change the route patterns.
getRecurringOrderReviewUrlPath() /recurring-orders/%s/review-required Storefront path used to build the review link in notification emails. Update it if you change the route patterns.

6) Import Back Office translations

The module ships Back Office translations at src/SprykerFeature/OrderExperienceManagement/data/translation/Zed/{locale}.csv. These cover the Recurring Order Schedules page, the table filters, and the Back Office configuration labels.

Rebuild the translation cache:

console translator:generate-cache
Verification

In the Back Office, open Sales > Recurring Order Schedules. Make sure the column headers, filter labels, and status badges are translated instead of showing raw glossary keys.

Install feature frontend

Follow the steps below to install the Recurring Orders feature frontend.

1) Set up routes

Register the following route provider plugin:

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
RecurringOrderRouteProviderPlugin Adds the storefront routes for the recurring order list, detail, edit, review, product search, product offer selection, shipment method, and schedule action pages. None SprykerFeature\Yves\OrderExperienceManagement\Plugin\Router

src/Pyz/Yves/Router/RouterDependencyProvider.php

<?php

namespace Pyz\Yves\Router;

use Spryker\Yves\Router\RouterDependencyProvider as SprykerRouterDependencyProvider;
use SprykerFeature\Yves\OrderExperienceManagement\Plugin\Router\RecurringOrderRouteProviderPlugin;

class RouterDependencyProvider extends SprykerRouterDependencyProvider
{
    /**
     * @return array<\Spryker\Yves\RouterExtension\Dependency\Plugin\RouteProviderPluginInterface>
     */
    protected function getRouteProvider(): array
    {
        return [
            // ...
            new RecurringOrderRouteProviderPlugin(), #RecurringOrdersFeature
        ];
    }
}

After registering the plugin, warm up the router caches:

vendor/bin/yves router:cache:warm-up
vendor/bin/console router:cache:warm-up
vendor/bin/console router:cache:warm-up:backend-gateway
Verification

Make sure the following storefront routes are accessible:

ROUTE NAME PATTERN PURPOSE
recurring-orders /recurring-orders Recurring order list page.
recurring-orders/detail /recurring-orders/{uuid} Recurring order detail page.
recurring-orders/review-required /recurring-orders/{uuid}/review-required Review required page.
recurring-order/edit /recurring-order/{uuid}/edit Recurring schedule edit form.
recurring-order/form /recurring-order-form Recurring order setup form rendered at checkout.
recurring-order/save /recurring-order/save Saves recurring order settings on the quote.
recurring-order/clear /recurring-order/clear Removes recurring order settings from the quote.
recurring-order/pause /recurring-order/{uuid}/pause Pauses a schedule.
recurring-order/resume /recurring-order/{uuid}/resume Resumes a paused schedule.
recurring-order/skip /recurring-order/{uuid}/skip Skips the next placement.
recurring-order/cancel /recurring-order/{uuid}/cancel Cancels a schedule.
recurring-order/confirm /recurring-order/{uuid}/confirm Confirms a reviewed schedule.
recurring-order/retry /recurring-order/{uuid}/retry Retries a failed placement.
recurring-orders/shipment-methods /recurring-orders/{uuid}/shipment-methods Returns the shipment methods available for an added item.
recurring-orders/product-price /recurring-orders/{uuid}/product-price Returns the current price of an added item.
recurring-orders/product-concrete-search /recurring-orders/{uuid}/product-concrete-search Backs the add-product search bar on the review page.
recurring-order/product-offer-select /recurring-order/product-offer-select Renders the merchant offer selector for an added item.

2) Set up widgets

Register the following global widgets:

WIDGET DESCRIPTION NAMESPACE
RecurringOrderSelectorWidget Renders the recurring order setup form at checkout. Visible only when the quote is eligible for a recurring order (invoice payment, not locked, not from RFQ, not guest). SprykerFeature\Yves\OrderExperienceManagement\Widget
RecurringOrderMenuItemWidget Renders the Recurring Orders navigation menu item in the storefront customer account menu. SprykerFeature\Yves\OrderExperienceManagement\Widget
ProductConcreteSearchWidget Backs the add-product search bar on the Review Required page. Also provides the products-list molecule the search results view reuses. SprykerShop\Yves\ProductSearchWidget\Widget
CostCenterDetailWidget Displays the selected cost center and budget in the recurring order detail sidebar, which is its only caller. Takes a QuoteTransfer and an optional flag that adds a budget usage summary. Only active cost centers are resolved, so a deactivated cost center makes the widget render nothing. Requires the Purchasing Control feature. SprykerFeature\Yves\PurchasingControl\Widget

src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php

<?php

namespace Pyz\Yves\ShopApplication;

use SprykerFeature\Yves\OrderExperienceManagement\Widget\RecurringOrderMenuItemWidget;
use SprykerFeature\Yves\OrderExperienceManagement\Widget\RecurringOrderSelectorWidget;
use SprykerFeature\Yves\PurchasingControl\Widget\CostCenterDetailWidget;
use SprykerShop\Yves\ProductSearchWidget\Widget\ProductConcreteSearchWidget;
use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;

class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{
    /**
     * @return array<string>
     */
    protected function getGlobalWidgets(): array
    {
        return [
            // ...
            RecurringOrderSelectorWidget::class, #RecurringOrdersFeature
            RecurringOrderMenuItemWidget::class, #RecurringOrdersFeature
            ProductConcreteSearchWidget::class, #RecurringOrdersFeature
            CostCenterDetailWidget::class, #RecurringOrdersFeature
        ];
    }
}
Verification
  1. On the checkout summary page with an invoice-based payment method, make sure the recurring order selector is displayed.
  2. In the customer account, make sure the Recurring Orders menu item is displayed.
  3. On the Review Required page, open the add-product search bar and make sure product suggestions are returned.
  4. On the recurring order detail page, make sure the selected cost center and budget names are displayed in the sidebar.

3) Set up storefront plugins

Optional features

Each plugin in this step comes from an optional feature. Register only the plugins whose feature your project uses.

Register the following plugins to let buyers select a cost center and budget on the recurring order forms, and to restrict which products they can add on the Review Required page:

PLUGIN SPECIFICATION PREREQUISITES NAMESPACE
CostCenterRecurringOrderApproveFormExpanderPlugin Adds cost center and budget dropdowns to the review approve form and validates the selected pair server-side. Only active cost centers of the buyer’s company business unit in the currency of the recurring order are offered. Purchasing Control feature SprykerFeature\Yves\PurchasingControl\Plugin\OrderExperienceManagement
CostCenterRecurringScheduleEditFormExpanderPlugin Adds cost center and budget dropdowns to the recurring schedule edit form and validates the selected pair server-side. Only active cost centers of the buyer’s company business unit in the currency of the recurring order are offered. Purchasing Control feature SprykerFeature\Yves\PurchasingControl\Plugin\OrderExperienceManagement
ServiceProductAddedProductConcreteRestrictionPlugin Hides a service product from the add-product search bar and the offer selector when its shipment types do not include one a recurring order can serve. SSP Service Management feature SprykerFeature\Yves\SelfServicePortal\Plugin\OrderExperienceManagement

src/Pyz/Yves/OrderExperienceManagement/OrderExperienceManagementDependencyProvider.php

<?php

namespace Pyz\Yves\OrderExperienceManagement;

use SprykerFeature\Yves\OrderExperienceManagement\OrderExperienceManagementDependencyProvider as SprykerOrderExperienceManagementDependencyProvider;
use SprykerFeature\Yves\PurchasingControl\Plugin\OrderExperienceManagement\CostCenterRecurringOrderApproveFormExpanderPlugin;
use SprykerFeature\Yves\PurchasingControl\Plugin\OrderExperienceManagement\CostCenterRecurringScheduleEditFormExpanderPlugin;
use SprykerFeature\Yves\SelfServicePortal\Plugin\OrderExperienceManagement\ServiceProductAddedProductConcreteRestrictionPlugin;

class OrderExperienceManagementDependencyProvider extends SprykerOrderExperienceManagementDependencyProvider
{
    /**
     * @return array<\SprykerFeature\Yves\OrderExperienceManagement\Dependency\Plugin\RecurringOrderApproveFormExpanderPluginInterface>
     */
    protected function getRecurringOrderApproveFormExpanderPlugins(): array
    {
        return [
            new CostCenterRecurringOrderApproveFormExpanderPlugin(), #RecurringOrdersFeature
        ];
    }

    /**
     * @return array<\SprykerFeature\Yves\OrderExperienceManagement\Dependency\Plugin\RecurringScheduleEditFormExpanderPluginInterface>
     */
    protected function getRecurringScheduleEditFormExpanderPlugins(): array
    {
        return [
            new CostCenterRecurringScheduleEditFormExpanderPlugin(), #RecurringOrdersFeature
        ];
    }

    /**
     * @return array<\SprykerFeature\Yves\OrderExperienceManagement\Dependency\Plugin\AddedProductConcreteRestrictionPluginInterface>
     */
    protected function getAddedProductConcreteRestrictionPlugins(): array
    {
        return [
            new ServiceProductAddedProductConcreteRestrictionPlugin(), #RecurringOrdersFeature
        ];
    }
}
Custom product restrictions

To hide additional products from the add-product picker, implement SprykerFeature\Yves\OrderExperienceManagement\Dependency\Plugin\AddedProductConcreteRestrictionPluginInterface. The plugin receives a ProductViewTransfer resolved from product storage with all view expanders applied, so it can rely on the data your own module publishes there. Returning true removes the product from the search results and makes the offer selector return no choices for it.

A storefront restriction only hides the product. To also reject a crafted request, register a matching AddedItemValidatorPluginInterface implementation in Zed—see Set up added item validator plugins.

Both cost center plugins offer only budgets whose enforcement rule is listed in getRecurringOrderSelectableBudgetEnforcementRules(). Budgets that require approval when the budget is exceeded are hidden, because a recurring order is placed unattended and cannot wait for an approval decision. To change which enforcement rules qualify, override the method in your project:

src/Pyz/Yves/PurchasingControl/PurchasingControlConfig.php

<?php

namespace Pyz\Yves\PurchasingControl;

use SprykerFeature\Shared\PurchasingControl\PurchasingControlConfig as SharedPurchasingControlConfig;
use SprykerFeature\Yves\PurchasingControl\PurchasingControlConfig as SprykerPurchasingControlConfig;

class PurchasingControlConfig extends SprykerPurchasingControlConfig
{
    /**
     * @return array<string>
     */
    public function getRecurringOrderSelectableBudgetEnforcementRules(): array
    {
        return [
            SharedPurchasingControlConfig::ENFORCEMENT_RULE_BLOCK,
            SharedPurchasingControlConfig::ENFORCEMENT_RULE_WARN,
        ];
    }
}
CONFIGURATION METHOD DEFAULT DESCRIPTION
getRecurringOrderSelectableBudgetEnforcementRules() [ENFORCEMENT_RULE_BLOCK, ENFORCEMENT_RULE_WARN] Budget enforcement rules that can be selected on the recurring order forms. Budgets bound to any other rule—ENFORCEMENT_RULE_REQUIRE_APPROVAL in particular—are hidden from the budget selector.
Verification
  1. Open the Review Required page for a schedule and make sure the Cost Center and Budget dropdowns are displayed on the approve form.
  2. Open the recurring schedule edit form and make sure the same dropdowns are displayed.
  3. Submit the form with a budget that does not belong to the selected cost center. Make sure the form is rejected with a validation error.
  4. Set a budget’s enforcement rule to Require approval in the Back Office. Make sure the budget is no longer offered in the recurring order budget selector.
  5. On the Review Required page, search for a service product whose shipment type is not in RECURRING_ORDER_SERVICE_SHIPMENT_TYPE_KEYS. Make sure the product is not offered in the add-product search results.

4) Add the recurring order selector to the checkout summary page

spryker-shop/checkout-page calls RecurringOrderSelectorWidget from its checkout summary template, so no change is required if your project uses the module template as is.

If your project overrides the checkout summary template, call the widget explicitly. Add it after any cost center or voucher sections, before the order form.

src/Pyz/Yves/CheckoutPage/Theme/default/views/summary/summary.twig

{% widget 'RecurringOrderSelectorWidget' args [data.cart] only %}{% endwidget %}
Verification

On the checkout summary page with an invoice-based payment method, make sure the Set up as recurring order checkbox and description are displayed.

5) Add the menu item to the customer navigation sidebar

spryker-shop/customer-page calls RecurringOrderMenuItemWidget from the customer-navigation molecule, so no change is required if your project uses the module template as is.

If your project overrides the customer-navigation molecule, call the widget from the same position—directly after the Order History item. The widget renders an <li> element and must be placed inside a <ul> context.

src/Pyz/Yves/CustomerPage/Theme/default/components/molecules/customer-navigation/customer-navigation.twig


{% widget 'RecurringOrderMenuItemWidget' args [data.activePage] only %}{% endwidget %}

Alternative: plain menu item

If your sidebar template is built from a plain data.items array instead of widget calls, add the entry directly. Place it after the Order History item:


{
    name: 'recurring-orders',
    url: path('recurring-orders'),
    label: 'recurring_orders.menu_item' | trans,
    icon: 'calendar',
},

Verification

In the storefront customer account, make sure the Recurring Orders menu item is displayed directly below Order History in the left sidebar navigation and links to /recurring-orders.

6) Configure the storefront behavior

Override the following configuration methods in your project (if needed) to adjust the storefront behavior:

src/Pyz/Yves/OrderExperienceManagement/OrderExperienceManagementConfig.php

<?php

namespace Pyz\Yves\OrderExperienceManagement;

use SprykerFeature\Yves\OrderExperienceManagement\OrderExperienceManagementConfig as SprykerOrderExperienceManagementConfig;

class OrderExperienceManagementConfig extends SprykerOrderExperienceManagementConfig
{
    /**
     * Specification:
     * - Returns the maximum number of company business units loaded into the recurring order search scope dropdown.
     * - Projects with more business units should raise this value or switch to an async autocomplete widget.
     *
     * @api
     */
    public function getBusinessUnitChoicesLimit(): int
    {
        return 250;
    }

    /**
     * Specification:
     * - Returns the number of recurring schedules shown per page on the list page.
     *
     * @api
     */
    public function getRecurringScheduleListItemsPerPage(): int
    {
        return 20;
    }
}
CONFIGURATION METHOD DEFAULT DESCRIPTION
getSupportedCadenceTypes() Weekly, bi-weekly, monthly, every N weeks Cadence types offered in the storefront recurring order forms, as [glossary key => cadence value]. Extend it when you register a custom cadence type plugin.
getCadenceTypeEveryNWeeks() every_n_weeks Cadence type that requires an additional numeric interval.
getInvoicePaymentMethodKeys() [invoice, purchaseOnAccount, dummyMarketplacePaymentInvoice] Payment method keys that qualify as invoice-based. Only quotes with a matching payment method can generate a recurring schedule. Override this in the shared config to apply it in Yves and Zed at once.
getBusinessUnitChoicesLimit() 100 Maximum number of company business units loaded into the recurring order search scope dropdown. Projects with more business units should raise this value or switch to an async autocomplete widget.
getRecurringScheduleListItemsPerPage() 10 Number of recurring schedules shown per page on the list page.
getRecurringScheduleHistoryItemsPerPage() 10 Number of execution history entries shown per page on the detail page.
getSupportedAddedItemShipmentTypeKeys() [delivery, on-site-service] Shipment type keys supported for products added on the Review Required page.
getSubstitutableReviewReasons() [discontinued, substituted] Review reason groups for which a Choose a substitute action is offered.
getPriceChangeReviewReasons() [price_increased] Review reason groups whose lines are marked as price changes the buyer accepts by confirming the order.
isUnavailableProductsExcludedFromAddProductSearch() true When enabled, concrete products and merchant offers without availability are hidden from the add-product search bar and the offer selector.
isMeasurementUnitProductAdditionRestricted() true When enabled, products sold in measurement units are hidden from the add-product picker. Reads the value from the shared config.
isPackagingUnitProductAdditionRestricted() true When enabled, products sold in packaging units are hidden from the add-product picker. Reads the value from the shared config.
getStatusBadgeClassMap(), getStatusIconMap(), getHistoryEventTypeBadgeClassMap(), getReviewReasonLabelMap(), getReviewReasonBadgeMap(), getItemFlagLabelMap(), getItemFlagBadgeMap() See OrderExperienceManagementConfig Presentation maps for statuses, history events, review reasons, and item flags. Extend them when you introduce custom statuses or review reasons.
getErrorBannerStatuses(), getAttentionBannerStatuses() See OrderExperienceManagementConfig Statuses that render the error banner and the attention banner on the list and detail pages.
getRecurringScheduleStatusChoices(), getReviewScopeChoices() See OrderExperienceManagementConfig Choices offered in the storefront status filter and the review scope selector.
Overriding storefront molecules

The module ships its storefront UI as molecules under src/SprykerFeature/OrderExperienceManagement/src/SprykerFeature/Yves/OrderExperienceManagement/Theme/default/components/molecules. To adjust the markup, override the molecule in your project at the same relative path under src/Pyz/Yves/OrderExperienceManagement/Theme/default/components/molecules. The most commonly customized molecules are recurring-order-selector, recurring-order-list, schedule-detail, schedule-detail-sidebar, schedule-review, and recurring-order-edit-form.

The Review Required page is composed of the review- molecules: review-flagged-items, review-unchanged-items, review-quantity-control, review-substitute, review-add-product, review-add-product-picker, review-shipment-selection, review-scope-selector, review-change-summary, review-summary-banner, review-blocking-errors, and review-footer.

The schedule-detail-sidebar, schedule-review, and recurring-order-edit-form molecules embed the cost-center-detail, recurring-order-cost-center-budget, and recurring-order-budget-summary molecules from the PurchasingControl module. If your project does not use the Purchasing Control feature, these embeds resolve to nothing and the surrounding markup remains valid.

7) Build the frontend

After making changes to Twig templates and registering new widgets, rebuild the Yves frontend assets:

npm run yves
Verification

Reload the checkout summary page and make sure the recurring order selector renders without console errors.

8) Import glossary data

Import the following glossary keys for Storefront translations:

data/import/common/common/glossary.csv

>recurring_orders.attention_banner.link.failed,View Failed,en_US
recurring_orders.attention_banner.link.failed,Fehlgeschlagene anzeigen,de_DE
recurring_orders.attention_banner.link.paused,View Paused,en_US
recurring_orders.attention_banner.link.paused,Pausierte anzeigen,de_DE
recurring_orders.attention_banner.link.review_required,View Review Required,en_US
recurring_orders.attention_banner.link.review_required,Zu prüfende anzeigen,de_DE
recurring_orders.attention_banner.message,You have %count% recurring schedule(s) that require your attention.,en_US
recurring_orders.attention_banner.message,"Sie haben %count% wiederkehrende(n) Zeitplan/Zeitpläne, die Ihre Aufmerksamkeit erfordern.",de_DE
recurring_orders.cadence.bi_weekly,Bi-weekly,en_US
recurring_orders.cadence.bi_weekly,Zweiwöchentlich,de_DE
recurring_orders.cadence.every_n_weeks,Every N weeks,en_US
recurring_orders.cadence.every_n_weeks,Alle N Wochen,de_DE
recurring_orders.cadence.monthly,Monthly,en_US
recurring_orders.cadence.monthly,Monatlich,de_DE
recurring_orders.cadence.weekly,Weekly,en_US
recurring_orders.cadence.weekly,Wöchentlich,de_DE
recurring_orders.checkout.cadence_label,Frequency,en_US
recurring_orders.checkout.cadence_label,Häufigkeit,de_DE
recurring_orders.checkout.cadence_placeholder,Select frequency,en_US
recurring_orders.checkout.cadence_placeholder,Häufigkeit auswählen,de_DE
recurring_orders.checkout.cadence_value_label,Interval (weeks),en_US
recurring_orders.checkout.cadence_value_label,Intervall (Wochen),de_DE
recurring_orders.checkout.confirm_button,Confirm,en_US
recurring_orders.checkout.confirm_button,Bestätigen,de_DE
recurring_orders.checkout.enable_description,"Get this order delivered automatically on your schedule. Skip and cancel anytime.",en_US
recurring_orders.checkout.enable_description,"Lassen Sie sich diese Bestellung automatisch nach Ihrem Zeitplan liefern. Jederzeit überspringen und stornieren.",de_DE
recurring_orders.checkout.enable_label,Set up as recurring order,en_US
recurring_orders.checkout.enable_label,Als wiederkehrende Bestellung einrichten,de_DE
recurring_orders.checkout.remove_button,Remove,en_US
recurring_orders.checkout.remove_button,Entfernen,de_DE
recurring_orders.checkout.schedule_name_label,Schedule name,en_US
recurring_orders.checkout.schedule_name_label,Name des Zeitplans,de_DE
recurring_orders.checkout.section_title,Recurring Order,en_US
recurring_orders.checkout.section_title,Wiederkehrende Bestellung,de_DE
recurring_orders.checkout.start_date_label,Start date,en_US
recurring_orders.checkout.start_date_label,Startdatum,de_DE
recurring_orders.checkout.start_date_tooltip,"The date your first recurring delivery is placed. Today's order is placed as normal, recurring deliveries then repeat on your selected frequency from this date.",en_US
recurring_orders.checkout.start_date_tooltip,"Das Datum, an dem Ihre erste wiederkehrende Lieferung erfolgt. Die heutige Bestellung wird wie gewohnt aufgegeben, wiederkehrende Lieferungen wiederholen sich danach ab diesem Datum in der von Ihnen gewählten Häufigkeit.",de_DE
recurring_orders.checkout.start_date_tooltip_label,More information about the start date,en_US
recurring_orders.checkout.start_date_tooltip_label,Weitere Informationen zum Startdatum,de_DE
recurring_orders.checkout.first_order_notice,Your first recurring order starts on %date%,en_US
recurring_orders.checkout.first_order_notice,Ihre erste wiederkehrende Bestellung beginnt am %date%,de_DE
recurring_orders.checkout.validation.start_date_required,Please choose a start date.,en_US
recurring_orders.checkout.validation.start_date_required,Bitte wählen Sie ein Startdatum aus.,de_DE
recurring_orders.checkout.validation.start_date_in_past,The start date cannot be in the past.,en_US
recurring_orders.checkout.validation.start_date_in_past,Das Startdatum darf nicht in der Vergangenheit liegen.,de_DE
recurring_orders.checkout.error.start_date_in_past,The recurring order start date cannot be in the past.,en_US
recurring_orders.checkout.error.start_date_in_past,Das Startdatum der wiederkehrenden Bestellung darf nicht in der Vergangenheit liegen.,de_DE
recurring_orders.checkout.error.start_date_required,Please choose a start date for the recurring order.,en_US
recurring_orders.checkout.error.start_date_required,Bitte wählen Sie ein Startdatum für die wiederkehrende Bestellung aus.,de_DE
recurring_orders.detail.access_denied,You do not have access to this recurring order.,en_US
recurring_orders.detail.access_denied,Sie haben keinen Zugriff auf diese wiederkehrende Bestellung.,de_DE
recurring_orders.detail.action.error,An error occurred while processing the action. Please try again.,en_US
recurring_orders.detail.action.error,Bei der Verarbeitung der Aktion ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.,de_DE
recurring_orders.detail.actions.cancel,Cancel Schedule,en_US
recurring_orders.detail.actions.cancel,Zeitplan abbrechen,de_DE
recurring_orders.detail.actions.pause,Pause,en_US
recurring_orders.detail.actions.pause,Pausieren,de_DE
recurring_orders.detail.actions.resume,Resume,en_US
recurring_orders.detail.actions.resume,Fortsetzen,de_DE
recurring_orders.detail.actions.retry,Retry Again,en_US
recurring_orders.detail.actions.retry,Erneut versuchen,de_DE
recurring_orders.detail.actions.review,Review,en_US
recurring_orders.detail.actions.review,Überprüfen,de_DE
recurring_orders.detail.actions.skip,Skip This Execution,en_US
recurring_orders.detail.actions.skip,Diese Ausführung überspringen,de_DE
recurring_orders.detail.cancel.modal.title,Cancel Schedule?,en_US
recurring_orders.detail.cancel.modal.title,Zeitplan abbrechen?,de_DE
recurring_orders.detail.cancel.modal.warning,This action is permanent. The schedule will stop immediately and cannot be reactivated.,en_US
recurring_orders.detail.cancel.modal.warning,Diese Aktion ist dauerhaft. Der Zeitplan wird sofort gestoppt und kann nicht reaktiviert werden.,de_DE
recurring_orders.detail.error.failed.title,The last order attempt failed for the following reasons:,en_US
recurring_orders.detail.error.failed.title,Der letzte Bestellversuch ist aus den folgenden Gründen fehlgeschlagen:,de_DE
recurring_orders.detail.estimated_total.info,Estimated Grand Total displays the sum of order items excl. shipping costs or discounts.,en_US
recurring_orders.detail.estimated_total.info,Der geschätzte Gesamtbetrag enthält die Summe der Artikel ohne Versandkosten oder Rabatte.,de_DE
recurring_orders.detail.estimated_total.label,Estimated Total,en_US
recurring_orders.detail.estimated_total.label,Geschätzter Gesamtbetrag,de_DE
recurring_orders.detail.skip.modal.title,Skip this execution?,en_US
recurring_orders.detail.skip.modal.title,Diese Ausführung überspringen?,de_DE
recurring_orders.detail.skip.modal.execution_being_skipped,Execution being skipped,en_US
recurring_orders.detail.skip.modal.execution_being_skipped,Übersprungene Ausführung,de_DE
recurring_orders.detail.skip.modal.next_scheduled_execution,Next scheduled execution,en_US
recurring_orders.detail.skip.modal.next_scheduled_execution,Nächste geplante Ausführung,de_DE
recurring_orders.detail.skip.modal.summary,The %skippedDate% order will not be placed. The schedule will continue and trigger next on %nextDate%.,en_US
recurring_orders.detail.skip.modal.summary,Die Bestellung vom %skippedDate% wird nicht aufgegeben. Der Zeitplan läuft weiter und wird als Nächstes am %nextDate% ausgelöst.,de_DE
recurring_orders.detail.skip.modal.confirm,Skip this execution,en_US
recurring_orders.detail.skip.modal.confirm,Diese Ausführung überspringen,de_DE
recurring_orders.detail.history.description,View all past and upcoming executions for this schedule,en_US
recurring_orders.detail.history.description,Alle vergangenen und bevorstehenden Ausführungen für diesen Zeitplan anzeigen,de_DE
recurring_orders.detail.history.actions,Actions,en_US
recurring_orders.detail.history.actions,Aktionen,de_DE
recurring_orders.detail.history.date,Date,en_US
recurring_orders.detail.history.date,Datum,de_DE
recurring_orders.detail.history.empty,No execution history yet.,en_US
recurring_orders.detail.history.empty,Noch kein Ausführungsverlauf vorhanden.,de_DE
recurring_orders.detail.history.order_reference,Order Reference,en_US
recurring_orders.detail.history.order_reference,Bestellreferenz,de_DE
recurring_orders.detail.history.status,Status,en_US
recurring_orders.detail.history.status,Status,de_DE
recurring_orders.detail.history.title,Past scheduled orders,en_US
recurring_orders.detail.history.title,Ausführungsverlauf,de_DE
recurring_orders.detail.history.view_order,View Order,en_US
recurring_orders.detail.history.view_order,Bestellung anzeigen,de_DE
recurring_orders.detail.next_execution.paused,Schedule is paused. Resume to set next execution date.,en_US
recurring_orders.detail.next_execution.paused,"Zeitplan ist pausiert. Setzen Sie ihn fort, um das nächste Ausführungsdatum festzulegen.",de_DE
recurring_orders.detail.next_execution.title,Next Scheduled Execution,en_US
recurring_orders.detail.next_execution.title,Nächste geplante Ausführung,de_DE
recurring_orders.detail.order_items.column.product,Product,en_US
recurring_orders.detail.order_items.column.product,Produkt,de_DE
recurring_orders.detail.order_items.column.quantity,Qty,en_US
recurring_orders.detail.order_items.column.quantity,Menge,de_DE
recurring_orders.detail.order_items.column.total,Total,en_US
recurring_orders.detail.order_items.column.total,Gesamt,de_DE
recurring_orders.detail.order_items.empty,No items found.,en_US
recurring_orders.detail.order_items.empty,Keine Artikel gefunden.,de_DE
recurring_orders.detail.order_items.label,Order Items,en_US
recurring_orders.detail.order_items.label,Bestellartikel,de_DE
recurring_orders.detail.order_items.not_in_next_order_label,Not in next order,en_US
recurring_orders.detail.order_items.not_in_next_order_label,Nicht in nächster Bestellung,de_DE
recurring_orders.detail.order_items.one_time_label,Just this order,en_US
recurring_orders.detail.order_items.one_time_label,Nur diese Bestellung,de_DE
recurring_orders.detail.order_items.recurring_quantity_label,Recurring: %quantity%,en_US
recurring_orders.detail.order_items.recurring_quantity_label,Wiederkehrend: %quantity%,de_DE
recurring_orders.detail.pause.modal.title,Pause Schedule?,en_US
recurring_orders.detail.pause.modal.title,Pausenplan?,de_DE
recurring_orders.detail.pause.modal.warning,Do you want to pause the schedule now?,en_US
recurring_orders.detail.pause.modal.warning,Möchten Sie den Zeitplan jetzt pausieren?,de_DE
recurring_orders.detail.resume.modal.title,Resume schedule,en_US
recurring_orders.detail.resume.modal.title,Zeitplan fortsetzen,de_DE
recurring_orders.detail.edit.title,Edit schedule,en_US
recurring_orders.detail.edit.title,Zeitplan bearbeiten,de_DE
recurring_orders.detail.edit.actions.save,Save changes,en_US
recurring_orders.detail.edit.actions.save,Änderungen speichern,de_DE
recurring_orders.detail.edit.success,Recurring order updated.,en_US
recurring_orders.detail.edit.success,Wiederkehrende Bestellung aktualisiert.,de_DE
recurring_orders.detail.edit.error,Recurring order could not be updated.,en_US
recurring_orders.detail.edit.error,Wiederkehrende Bestellung konnte nicht aktualisiert werden.,de_DE
recurring_orders.detail.edit.error.schedule_not_found,Recurring order %uuid% not found or access denied.,en_US
recurring_orders.detail.edit.error.schedule_not_found,Wiederkehrende Bestellung %uuid% nicht gefunden oder Zugriff verweigert.,de_DE
recurring_orders.detail.edit.name_label,Name,en_US
recurring_orders.detail.edit.name_label,Name,de_DE
recurring_orders.detail.edit.frequency_label,Frequency,en_US
recurring_orders.detail.edit.frequency_label,Häufigkeit,de_DE
recurring_orders.detail.edit.cadence_value_label,Interval,en_US
recurring_orders.detail.edit.cadence_value_label,Intervall,de_DE
recurring_orders.detail.edit.starting_date_label,Next Execution Date,en_US
recurring_orders.detail.edit.starting_date_label,Nächstes Ausführungsdatum,de_DE
recurring_orders.detail.edit.validation.name_required,Please enter a name.,en_US
recurring_orders.detail.edit.validation.name_required,Bitte geben Sie einen Namen ein.,de_DE
recurring_orders.detail.edit.validation.starting_date_required,Please select a starting date.,en_US
recurring_orders.detail.edit.validation.starting_date_required,Bitte wählen Sie ein Startdatum.,de_DE
recurring_orders.detail.edit.validation.starting_date_in_past,The starting date cannot be in the past.,en_US
recurring_orders.detail.edit.validation.starting_date_in_past,Das Startdatum darf nicht in der Vergangenheit liegen.,de_DE
recurring_orders.detail.resume.date_label,Next execution date,en_US
recurring_orders.detail.resume.date_label,Datum der nächsten Ausführung,de_DE
recurring_orders.detail.review_required.attention,Recurring order could not be processed automatically and requires your attention.,en_US
recurring_orders.detail.review_required.attention,Der wiederkehrende Auftrag konnte nicht automatisch verarbeitet werden und erfordert Ihre Aufmerksamkeit.,de_DE
recurring_orders.detail.sidebar.cadence,Cadence,en_US
recurring_orders.detail.sidebar.cadence,Häufigkeit,de_DE
recurring_orders.detail.sidebar.created,Created,en_US
recurring_orders.detail.sidebar.created,Erstellt,de_DE
recurring_orders.detail.sidebar.created_by,Created By,en_US
recurring_orders.detail.sidebar.created_by,Erstellt von,de_DE
recurring_orders.detail.sidebar.status,Status,en_US
recurring_orders.detail.sidebar.status,Status,de_DE
recurring_orders.detail.sidebar.title,Schedule Details,en_US
recurring_orders.detail.sidebar.title,Zeitplandetails,de_DE
recurring_orders.detail.source_order,Original order,en_US
recurring_orders.detail.source_order,Ursprüngliche Bestellung,de_DE
recurring_orders.detail.title,Recurring Order Details,en_US
recurring_orders.detail.title,Wiederkehrende Bestellung – Details,de_DE
recurring_orders.error.items_not_purchasable,The following items can no longer be purchased and the recurring order was not placed: %skus%,en_US
recurring_orders.error.items_not_purchasable,"Die folgenden Artikel können nicht mehr gekauft werden, und die wiederkehrende Bestellung wurde nicht aufgegeben: %skus%",de_DE
recurring_orders.history.errors.show,Show errors,en_US
recurring_orders.history.errors.show,Fehler anzeigen,de_DE
recurring_orders.history.event.cancelled,Cancelled,en_US
recurring_orders.history.event.cancelled,Abgebrochen,de_DE
recurring_orders.history.event.failed,Failed,en_US
recurring_orders.history.event.failed,Fehlgeschlagen,de_DE
recurring_orders.history.event.paused,Paused,en_US
recurring_orders.history.event.paused,Pausiert,de_DE
recurring_orders.history.event.placed,Completed,en_US
recurring_orders.history.event.placed,Abgeschlossen,de_DE
recurring_orders.history.event.resumed,Resumed,en_US
recurring_orders.history.event.resumed,Fortgesetzt,de_DE
recurring_orders.history.event.skipped,Skipped,en_US
recurring_orders.history.event.skipped,Übersprungen,de_DE
recurring_orders.item.configurable_bundle_label,Bundle: %name%,en_US
recurring_orders.item.configurable_bundle_label,Bundle: %name%,de_DE
recurring_orders.list.column.actions,Actions,en_US
recurring_orders.list.column.actions,Aktionen,de_DE
recurring_orders.list.column.frequency,Frequency,en_US
recurring_orders.list.column.frequency,Häufigkeit,de_DE
recurring_orders.list.column.name,Schedule Name,en_US
recurring_orders.list.column.name,Zeitplanname,de_DE
recurring_orders.list.column.next_order_date,Next Order Date,en_US
recurring_orders.list.column.next_order_date,Nächstes Bestelldatum,de_DE
recurring_orders.list.column.status,Status,en_US
recurring_orders.list.column.status,Status,de_DE
recurring_orders.list.empty,No recurring orders found.,en_US
recurring_orders.list.empty,Keine wiederkehrenden Bestellungen gefunden.,de_DE
recurring_orders.list.form.scope,Show schedules for,en_US
recurring_orders.list.form.scope,Zeitpläne anzeigen für,de_DE
recurring_orders.list.form.search,Search,en_US
recurring_orders.list.form.search,Suche,de_DE
recurring_orders.list.form.search_placeholder,Search schedules...,en_US
recurring_orders.list.form.search_placeholder,Zeitpläne suchen...,de_DE
recurring_orders.list.form.status,Status,en_US
recurring_orders.list.form.status,Status,de_DE
recurring_orders.list.form.status.all,All statuses,en_US
recurring_orders.list.form.status.all,Alle Status,de_DE
recurring_orders.list.scope.company,All Company Schedules,en_US
recurring_orders.list.scope.company,Alle Unternehmensbestellungen,de_DE
recurring_orders.list.scope.my_business_unit,My Business Unit,en_US
recurring_orders.list.scope.my_business_unit,Meine Geschäftseinheit,de_DE
recurring_orders.list.scope.my_schedules,My Schedules,en_US
recurring_orders.list.scope.my_schedules,Meine Zeitpläne,de_DE
recurring_orders.list.title,Recurring Orders,en_US
recurring_orders.list.title,Wiederkehrende Bestellungen,de_DE
recurring_orders.mail.notify_buyer_placement_failure.body.cta,Review Order,en_US
recurring_orders.mail.notify_buyer_placement_failure.body.cta,Bestellung prüfen,de_DE
recurring_orders.mail.notify_buyer_placement_failure.body.detail,Please review the order details and failure reason in your recurring orders dashboard.,en_US
recurring_orders.mail.notify_buyer_placement_failure.body.detail,Bitte überprüfen Sie die Bestelldetails und den Fehlergrund in Ihrem Dashboard für Folgebestellungen.,de_DE
recurring_orders.mail.notify_buyer_placement_failure.body.intro,"We were unable to place your recurring order ""%schedule_name%"" automatically.",en_US
recurring_orders.mail.notify_buyer_placement_failure.body.intro,"Ihre Folgebestellung ""%schedule_name%"" konnte nicht automatisch aufgegeben werden.",de_DE
recurring_orders.mail.notify_buyer_placement_failure.body.sign_off,Best regards,en_US
recurring_orders.mail.notify_buyer_placement_failure.body.sign_off,Mit freundlichen Grüßen,de_DE
recurring_orders.mail.notify_buyer_placement_failure.salutation,Hello,en_US
recurring_orders.mail.notify_buyer_placement_failure.salutation,Hallo,de_DE
recurring_orders.mail.notify_buyer_placement_failure.subject,"Recurring order ""%schedule_name%"" could not be placed",en_US
recurring_orders.mail.notify_buyer_placement_failure.subject,"Folgebestellung ""%schedule_name%"" konnte nicht aufgegeben werden",de_DE
recurring_orders.mail.notify_buyer_upcoming_order.body.cta,View Order,en_US
recurring_orders.mail.notify_buyer_upcoming_order.body.cta,Bestellung ansehen,de_DE
recurring_orders.mail.notify_buyer_upcoming_order.body.intro,"Your recurring order ""%schedule_name%"" is scheduled for %execution_date%. We will process it automatically on that date.",en_US
recurring_orders.mail.notify_buyer_upcoming_order.body.intro,"Ihre wiederkehrende Bestellung ""%schedule_name%"" ist für %execution_date% geplant. Wir werden sie automatisch an diesem Datum verarbeiten.",de_DE
recurring_orders.mail.notify_buyer_upcoming_order.body.review,"If you need to make changes, please review and update your order before %expiry_date%.",en_US
recurring_orders.mail.notify_buyer_upcoming_order.body.review,"Wenn Sie Änderungen vornehmen möchten, überprüfen und aktualisieren Sie bitte Ihre Bestellung vor dem %expiry_date%.",de_DE
recurring_orders.mail.notify_buyer_upcoming_order.salutation,Hello,en_US
recurring_orders.mail.notify_buyer_upcoming_order.salutation,Hallo,de_DE
recurring_orders.mail.notify_buyer_upcoming_order.subject,"Upcoming recurring order: ""%schedule_name%"" on %execution_date%",en_US
recurring_orders.mail.notify_buyer_upcoming_order.subject,"Bevorstehende Folgebestellung: ""%schedule_name%"" am %execution_date%",de_DE
recurring_orders.mail.notify_buyer_validation_failed.body.cta,Review Order,en_US
recurring_orders.mail.notify_buyer_validation_failed.body.cta,Bestellung überprüfen,de_DE
recurring_orders.mail.notify_buyer_validation_failed.body.detail,Please review your order details and make any necessary adjustments before the scheduled date.,en_US
recurring_orders.mail.notify_buyer_validation_failed.body.detail,Bitte überprüfen Sie Ihre Bestelldetails und nehmen Sie gegebenenfalls Anpassungen vor dem geplanten Datum vor.,de_DE
recurring_orders.mail.notify_buyer_validation_failed.body.intro,"Your recurring order ""%schedule_name%"", scheduled for %execution_date%, could not be processed automatically and requires your attention.",en_US
recurring_orders.mail.notify_buyer_validation_failed.body.intro,"Ihre wiederkehrende Bestellung ""%schedule_name%"", geplant für %execution_date%, konnte nicht automatisch verarbeitet werden und erfordert Ihre Aufmerksamkeit.",de_DE
recurring_orders.mail.notify_buyer_validation_failed.salutation,Hello,en_US
recurring_orders.mail.notify_buyer_validation_failed.salutation,Hallo,de_DE
recurring_orders.mail.notify_buyer_validation_failed.subject,"Action Required: ""%schedule_name%"" — your order needs attention",en_US
recurring_orders.mail.notify_buyer_validation_failed.subject,"Handlung erforderlich: ""%schedule_name%"" – Ihre Bestellung erfordert Aufmerksamkeit",de_DE
recurring_orders.menu_item,Recurring Orders,en_US
recurring_orders.menu_item,Wiederkehrende Bestellungen,de_DE
recurring_orders.review_details,Review recurring order,en_US
recurring_orders.review_details,Wiederkehrende Bestellung prüfen,de_DE
recurring_orders.sku,SKU: %sku%,en_US
recurring_orders.sku,SKU: %sku%,de_DE
recurring_orders.summary_of_changes,Summary of changes,en_US
recurring_orders.summary_of_changes,Zusammenfassung der Änderungen,de_DE
recurring_orders.view_details,Schedule Details,en_US
recurring_orders.view_details,Zeitplandetails,de_DE
recurring_orders.review.all_items_removed,The order cannot be placed - all items are unavailable and the schedule cannot be executed without items.,en_US
recurring_orders.review.all_items_removed,Die Bestellung kann nicht aufgegeben werden - alle Artikel sind nicht verfügbar und der Zeitplan kann ohne Artikel nicht ausgeführt werden.,de_DE
recurring_orders.review.approve_error,The changes could not be applied. Please try again.,en_US
recurring_orders.review.approve_error,Die Änderungen konnten nicht übernommen werden. Bitte versuchen Sie es erneut.,de_DE
recurring_orders.review.add_product.error.no_price,No price is available for product %sku%. Please remove it or choose a different product.,en_US
recurring_orders.review.add_product.error.no_price,Für das Produkt %sku% ist kein Preis verfügbar. Bitte entfernen Sie es oder wählen Sie ein anderes Produkt.,de_DE
recurring_orders.review.add_product.error.not_available,Product %sku% is not available. Please remove it or choose a different product.,en_US
recurring_orders.review.add_product.error.not_available,Das Produkt %sku% ist nicht verfügbar. Bitte entfernen Sie es oder wählen Sie ein anderes Produkt.,de_DE
recurring_orders.review.add_product.error.shipment_unavailable,No shipment method is available for product %sku%. Please remove it or choose a different product.,en_US
recurring_orders.review.add_product.error.shipment_unavailable,Für das Produkt %sku% ist keine Versandart verfügbar. Bitte entfernen Sie es oder wählen Sie ein anderes Produkt.,de_DE
recurring_orders.review.add_product.error.measurement_unit_not_supported,Product %sku% is sold in measurement units and cannot be added here. Please choose a different product.,en_US
recurring_orders.review.add_product.error.measurement_unit_not_supported,Das Produkt %sku% wird in Maßeinheiten verkauft und kann hier nicht hinzugefügt werden. Bitte wählen Sie ein anderes Produkt.,de_DE
recurring_orders.review.add_product.error.packaging_unit_not_supported,Product %sku% is sold in packaging units and cannot be added here. Please choose a different product.,en_US
recurring_orders.review.add_product.error.packaging_unit_not_supported,Das Produkt %sku% wird in Verpackungseinheiten verkauft und kann hier nicht hinzugefügt werden. Bitte wählen Sie ein anderes Produkt.,de_DE
recurring_orders.review.add_product.error.not_placeable,The added products cannot be scheduled. Please review your selection and try again.,en_US
recurring_orders.review.add_product.error.not_placeable,Die hinzugefügten Produkte können nicht eingeplant werden. Bitte überprüfen Sie Ihre Auswahl und versuchen Sie es erneut.,de_DE
recurring_orders.review.approve_failed,The changes could not be applied.,en_US
recurring_orders.review.approve_failed,Die Änderungen konnten nicht übernommen werden.,de_DE
recurring_orders.review.approve_success,Your changes were applied and the order was placed.,en_US
recurring_orders.review.approve_success,Ihre Änderungen wurden übernommen und die Bestellung wurde aufgegeben.,de_DE
recurring_orders.review.back_to_detail,Back to schedule detail,en_US
recurring_orders.review.back_to_detail,Zurück zur Zeitplanübersicht,de_DE
recurring_orders.review.banner.price_change,%count% price change,en_US
recurring_orders.review.banner.price_change,%count% Preisänderung,de_DE
recurring_orders.review.banner.substituted,%count% product substituted,en_US
recurring_orders.review.banner.substituted,%count% Produkt ersetzt,de_DE
recurring_orders.review.banner.unavailable,%count% unavailable,en_US
recurring_orders.review.banner.unavailable,%count% nicht verfügbar,de_DE
recurring_orders.review.blocking.title,Cannot proceed — blocking issues,en_US
recurring_orders.review.blocking.title,Kann nicht fortfahren — blockierende Probleme,de_DE
recurring_orders.review.column.current_price,Current price,en_US
recurring_orders.review.column.current_price,Aktueller Preis,de_DE
recurring_orders.review.column.previous_price,Previous price,en_US
recurring_orders.review.column.previous_price,Vorheriger Preis,de_DE
recurring_orders.review.column.reason,Reason,en_US
recurring_orders.review.column.reason,Grund,de_DE
recurring_orders.review.cta.accept_and_place,Accept & place order,en_US
recurring_orders.review.cta.accept_and_place,Bestellung annehmen und aufgeben,de_DE
recurring_orders.review.execution_scheduled,Execution scheduled for %date%,en_US
recurring_orders.review.execution_scheduled,Ausführung geplant für %date%,de_DE
recurring_orders.review.flagged.title,Items requiring your review,en_US
recurring_orders.review.flagged.title,Zu überprüfende Artikel,de_DE
recurring_orders.review.footer.helper,Accepting these changes will update this order and future executions of the schedule.,en_US
recurring_orders.review.footer.helper,"Wenn Sie diese Änderungen akzeptieren, werden diese Bestellung und zukünftige Ausführungen des Zeitplans aktualisiert.",de_DE
recurring_orders.review.footer.new_total,New total,en_US
recurring_orders.review.footer.new_total,Neue Summe,de_DE
recurring_orders.review.footer.summary,Removed products: %removed%,en_US
recurring_orders.review.footer.summary,Entfernte Produkte: %removed%,de_DE
recurring_orders.review.invalid_form,The request could not be processed. Please review and try again.,en_US
recurring_orders.review.invalid_form,Die Anfrage konnte nicht verarbeitet werden. Bitte überprüfen Sie sie und versuchen Sie es erneut.,de_DE
recurring_orders.review.modal.accepted_prices,Accepted price changes,en_US
recurring_orders.review.modal.accepted_prices,Akzeptierte Preisänderungen,de_DE
recurring_orders.review.modal.body,Removing unavailable items and accepting updated prices will affect this order and all future executions of this schedule. These changes will become the new reference state for the recurring order going forward.,en_US
recurring_orders.review.modal.body,Das Entfernen nicht verfügbarer Artikel und das Akzeptieren aktualisierter Preise wirken sich auf diese Bestellung und alle zukünftigen Ausführungen dieses Zeitplans aus. Diese Änderungen werden zum neuen Referenzzustand der wiederkehrenden Bestellung.,de_DE
recurring_orders.review.modal.headline,Confirm order and apply changes,en_US
recurring_orders.review.modal.headline,Bestellung bestätigen und Änderungen übernehmen,de_DE
recurring_orders.review.modal.new_total,New order total,en_US
recurring_orders.review.modal.new_total,Neue Bestellsumme,de_DE
recurring_orders.review.modal.original_total,Original order total,en_US
recurring_orders.review.modal.original_total,Ursprüngliche Bestellsumme,de_DE
recurring_orders.review.modal.removed_items,Removed products,en_US
recurring_orders.review.modal.removed_items,Entfernte Produkte,de_DE
recurring_orders.review.modal.substituted_items,Substituted products,en_US
recurring_orders.review.modal.substituted_items,Ersetzte Produkte,de_DE
recurring_orders.review.modal.added_products,Added products,en_US
recurring_orders.review.modal.added_products,Hinzugefügte Produkte,de_DE
recurring_orders.review.quantity_label,Quantity (every future order),en_US
recurring_orders.review.quantity_label,Menge (jede zukünftige Bestellung),de_DE
recurring_orders.review.scope.label,Apply these changes to,en_US
recurring_orders.review.scope.label,Diese Änderungen anwenden auf,de_DE
recurring_orders.review.scope.this_order,This order only,en_US
recurring_orders.review.scope.this_order,Nur diese Bestellung,de_DE
recurring_orders.review.scope.every_future,Update schedule permanently,en_US
recurring_orders.review.scope.every_future,Zeitplan dauerhaft aktualisieren,de_DE
recurring_orders.review-scope-selector-option.occurrence.note,"Apply to the %date% execution. Your recurring schedule stays unchanged.",en_US
recurring_orders.review-scope-selector-option.occurrence.note,"Auf die Ausführung am %date% anwenden. Ihr wiederkehrender Zeitplan bleibt unverändert.",de_DE
recurring_orders.review-scope-selector-option.standing.note,"Apply to this and all future executions, and update reference prices.",en_US
recurring_orders.review-scope-selector-option.standing.note,"Wenden Sie dies auf diese und alle zukünftigen Ausführungen an und aktualisieren Sie die Referenzpreise.",de_DE
recurring_orders.review.remove.label,Remove from schedule,en_US
recurring_orders.review.remove.label,Aus Zeitplan entfernen,de_DE
recurring_orders.review.remove.undo,Undo removal,en_US
recurring_orders.review.remove.undo,Entfernen rückgängig machen,de_DE
recurring_orders.review.quantity.decrease,Decrease quantity,en_US
recurring_orders.review.quantity.decrease,Menge verringern,de_DE
recurring_orders.review.quantity.increase,Increase quantity,en_US
recurring_orders.review.quantity.increase,Menge erhöhen,de_DE
recurring_orders.review.scope_required,Please choose whether each quantity change applies to just this order or every future order.,en_US
recurring_orders.review.scope_required,Bitte wählen Sie ob jede Mengenänderung nur für diese Bestellung oder für jede zukünftige Bestellung gilt.,de_DE
recurring_orders.review.quantity_invalid,Please enter a quantity of at least 1. To drop an item use the remove action instead.,en_US
recurring_orders.review.quantity_invalid,Bitte geben Sie eine Menge von mindestens 1 ein. Verwenden Sie zum Entfernen eines Artikels die Entfernen-Aktion.,de_DE
recurring_orders.review.not_available,This schedule does not require review.,en_US
recurring_orders.review.not_available,Dieser Zeitplan erfordert keine Überprüfung.,de_DE
recurring_orders.review.currency_mismatch,This recurring order can only be reviewed in its own currency. Please switch back to it to make changes.,en_US
recurring_orders.review.currency_mismatch,"Diese wiederkehrende Bestellung kann nur in ihrer eigenen Währung überprüft werden. Bitte wechseln Sie zurück, um Änderungen vorzunehmen.",de_DE
recurring_orders.review.price_mode_mismatch,This recurring order can only be reviewed in its own price mode (gross/net). Please switch back to it to make changes.,en_US
recurring_orders.review.price_mode_mismatch,"Diese wiederkehrende Bestellung kann nur in ihrem eigenen Preismodus (brutto/netto) überprüft werden. Bitte wechseln Sie zurück, um Änderungen vorzunehmen.",de_DE
recurring_orders.review.prices_changed,Prices changed again since you reviewed. Please review the updated order before approving.,en_US
recurring_orders.review.prices_changed,Die Preise haben sich seit Ihrer Überprüfung erneut geändert. Bitte überprüfen Sie die aktualisierte Bestellung vor der Genehmigung.,de_DE
recurring_orders.review.reason.discontinued,Discontinued,en_US
recurring_orders.review.reason.discontinued,Eingestellt,de_DE
recurring_orders.review.reason.not_approved,Not approved,en_US
recurring_orders.review.reason.not_approved,Nicht genehmigt,de_DE
recurring_orders.review.reason.price_increased,Price increased,en_US
recurring_orders.review.reason.price_increased,Preis erhöht,de_DE
recurring_orders.review.reason.price_unavailable,Price unavailable,en_US
recurring_orders.review.reason.price_unavailable,Preis nicht verfügbar,de_DE
recurring_orders.review.reason.substituted,Substituted,en_US
recurring_orders.review.reason.substituted,Ersetzt,de_DE
recurring_orders.review.substitute.label,Substitute,en_US
recurring_orders.review.substitute.label,Ersatzprodukt,de_DE
recurring_orders.review.substitute.select,Select Substitute,en_US
recurring_orders.review.substitute.select,Ersatzprodukt auswählen,de_DE
recurring_orders.review.substitute.change,Change,en_US
recurring_orders.review.substitute.change,Ändern,de_DE
recurring_orders.review.substitute.remove,Remove,en_US
recurring_orders.review.substitute.remove,Entfernen,de_DE
recurring_orders.review.reason.unavailable,Unavailable,en_US
recurring_orders.review.reason.unavailable,Nicht verfügbar,de_DE
recurring_orders.review.reason.out_of_stock,Out Of Stock,en_US
recurring_orders.review.reason.out_of_stock,Nicht auf Lager,de_DE
recurring_orders.review.title,Review Required — %name%,en_US
recurring_orders.review.title,Überprüfung erforderlich — %name%,de_DE
recurring_orders.review.unchanged.title,Unchanged items (%count%),en_US
recurring_orders.review.unchanged.title,Unveränderte Artikel (%count%),de_DE
recurring_orders.review.unchanged.title.plural,%count% unchanged items,en_US
recurring_orders.review.unchanged.title.plural,%count% unveränderte Artikel,de_DE
recurring_orders.status.active,Active,en_US
recurring_orders.status.active,Aktiv,de_DE
recurring_orders.status.cancelled,Cancelled,en_US
recurring_orders.status.cancelled,Abgebrochen,de_DE
recurring_orders.status.draft,Draft,en_US
recurring_orders.status.draft,Entwurf,de_DE
recurring_orders.status.failed,Failed,en_US
recurring_orders.status.failed,Fehlgeschlagen,de_DE
recurring_orders.status.paused,Paused,en_US
recurring_orders.status.paused,Pausiert,de_DE
recurring_orders.status.review_required,Review Required,en_US
recurring_orders.status.review_required,Überprüfung erforderlich,de_DE
recurring_orders.checkout.validation.cadence_required,Please select a frequency.,en_US
recurring_orders.checkout.validation.cadence_required,Bitte wählen Sie eine Häufigkeit aus.,de_DE
recurring_orders.checkout.validation.settings_not_confirmed,Please confirm your recurring order settings before submitting.,en_US
recurring_orders.checkout.validation.settings_not_confirmed,Bitte bestätigen Sie Ihre Einstellungen für die wiederkehrende Bestellung vor dem Absenden.,de_DE
recurring_orders.detail.resume.validation.date_required,Please select a resume date.,en_US
recurring_orders.detail.resume.validation.date_required,Bitte wählen Sie ein Fortsetzungsdatum aus.,de_DE
recurring_orders.detail.resume.validation.date_in_past,The resume date must be in the future.,en_US
recurring_orders.detail.resume.validation.date_in_past,Das Fortsetzungsdatum muss in der Zukunft liegen.,de_DE
recurring_orders.scheduled.order.errors.title,Recurring order not placed,en_US
recurring_orders.scheduled.order.errors.title,Wiederkehrende Bestellung nicht aufgegeben,de_DE
recurring_orders.review.add_product.add,Add,en_US
recurring_orders.review.add_product.add,Hinzufügen,de_DE
recurring_orders.review.add_product.clear,Clear,en_US
recurring_orders.review.add_product.clear,Zurücksetzen,de_DE
recurring_orders.review.add_product.label,Add a product — name or SKU,en_US
recurring_orders.review.add_product.label,Produkt hinzufügen — Name oder SKU,de_DE
recurring_orders.review.add_product.modal.title,Add a product,en_US
recurring_orders.review.add_product.modal.title,Produkt hinzufügen,de_DE
recurring_orders.review.add_product.open_modal,Add product,en_US
recurring_orders.review.add_product.open_modal,Produkt hinzufügen,de_DE
recurring_orders.review.add_product.price,Price,en_US
recurring_orders.review.add_product.price,Preis,de_DE
recurring_orders.review.add_product.quantity,Quantity,en_US
recurring_orders.review.add_product.quantity,Menge,de_DE
recurring_orders.review.add_product.remove,Remove,en_US
recurring_orders.review.add_product.remove,Entfernen,de_DE
recurring_orders.review.add_product.shipment_address,Delivery address,en_US
recurring_orders.review.add_product.shipment_address,Lieferadresse,de_DE
recurring_orders.review.add_product.shipment_address.group.company_unit_address,Business unit addresses,en_US
recurring_orders.review.add_product.shipment_address.group.company_unit_address,Adressen der Geschäftseinheit,de_DE
recurring_orders.review.add_product.shipment_address.group.schedule,Saved with this schedule,en_US
recurring_orders.review.add_product.shipment_address.group.schedule,Mit diesem Zeitplan gespeichert,de_DE
recurring_orders.review.add_product.shipment_address.placeholder,Select a delivery address,en_US
recurring_orders.review.add_product.shipment_address.placeholder,Lieferadresse auswählen,de_DE
recurring_orders.review.add_product.shipment_method,Shipment method,en_US
recurring_orders.review.add_product.shipment_method,Versandart,de_DE
recurring_orders.review.add_product.shipment_method.none,No shipment methods available for this address.,en_US
recurring_orders.review.add_product.shipment_method.none,Für diese Adresse sind keine Versandarten verfügbar.,de_DE
recurring_orders.review.add_product.shipment_method.placeholder,Select a shipment method,en_US
recurring_orders.review.add_product.shipment_method.placeholder,Versandart auswählen,de_DE
recurring_orders.review.add_product.title,Added products (%count%),en_US
recurring_orders.review.add_product.title,Hinzugefügte Produkte (%count%),de_DE
recurring_orders.review.add_product.title.singular,Added product (%count%),en_US
recurring_orders.review.add_product.title.singular,Hinzugefügtes Produkt (%count%),de_DE
recurring_orders.review.substitute.delta_higher,Higher price,en_US
recurring_orders.review.substitute.delta_higher,Höherer Preis,de_DE
recurring_orders.review.substitute.delta_lower,Lower price,en_US
recurring_orders.review.substitute.delta_lower,Niedrigerer Preis,de_DE
recurring_orders.review.substitute.delta_same,Same price,en_US
recurring_orders.review.substitute.delta_same,Gleicher Preis,de_DE
recurring_orders.review.substitute.modal.confirm,Use this substitute,en_US
recurring_orders.review.substitute.modal.confirm,Diesen Ersatz verwenden,de_DE
recurring_orders.review.substitute.modal.description,%product% is no longer available. Select a replacement to use for this order.,en_US
recurring_orders.review.substitute.modal.description,%product% ist nicht mehr verfügbar. Wählen Sie einen Ersatz für diese Bestellung.,de_DE
recurring_orders.review.substitute.modal.title,Choose a substitute,en_US
recurring_orders.review.substitute.modal.title,Ersatzprodukt wählen,de_DE
recurring_orders.review.substitute.recommended,recommended,en_US
recurring_orders.review.substitute.recommended,empfohlen,de_DE
recurring_orders.review.substitute.removed,Removed,en_US
recurring_orders.review.substitute.removed,Entfernt,de_DE
recurring_orders.review.substitute.unavailable,unavailable,en_US
recurring_orders.review.substitute.unavailable,nicht verfügbar,de_DE
recurring_orders.checkout.error.not_eligible,This cart cannot be placed as a recurring order.,en_US
recurring_orders.checkout.error.not_eligible,Dieser Warenkorb kann nicht als wiederkehrende Bestellung aufgegeben werden.,de_DE
recurring_orders.checkout.error.cadence_value_required,Please choose an interval for the recurring order.,en_US
recurring_orders.checkout.error.cadence_value_required,Bitte wählen Sie ein Intervall für die wiederkehrende Bestellung aus.,de_DE
recurring_orders.error.quote_not_found,The cart could not be found or access was denied.,en_US
recurring_orders.error.quote_not_found,Der Warenkorb wurde nicht gefunden oder der Zugriff wurde verweigert.,de_DE
recurring_orders.review.reason.configurable_bundle_unavailable,Configurable bundle unavailable,en_US
recurring_orders.review.reason.configurable_bundle_unavailable,Konfigurierbares Bundle nicht verfügbar,de_DE
recurring_orders.review.product-discontinued.message,"This product is discontinued. Choose a replacement to keep it in your order.",en_US
recurring_orders.review.product-discontinued.message,"Dieses Produkt wurde eingestellt. Wählen Sie einen Ersatz aus, damit es in Ihrer Bestellung bleibt.",de_DE
recurring_orders.review.substitute.sold-by,Sold by,en_US
recurring_orders.review.substitute.sold-by,Verkauft durch,de_DE

Import data:

console data:import:glossary
Verification

Make sure that, in the database, the configured data has been added to the spy_glossary_key and spy_glossary_translation tables.

9) Configure the Back Office settings

Sync the recurring orders configuration settings to the database to make them editable in the Back Office:

console configuration:sync
Verification

In the Back Office, go to Configuration > Recurring Orders > General > Schedule. Make sure the Schedule Grace Period field is displayed with a default value of 48.

Configurable settings
SETTING KEY DEFAULT CONSTRAINTS DESCRIPTION
Schedule Grace Period grace_period_hours 48 Required, between 1 and 720 Number of hours before the trigger date when the pre-trigger notification email is sent to the buyer. Per-schedule overrides stored in spy_recurring_schedule.notification_window_hours take precedence over this global value.

The setting is defined at src/SprykerFeature/OrderExperienceManagement/resources/configuration/recurring_orders.configuration.yml and applies globally—it has no per-store scope.