This concept introduces the recurring order feature, available through the Recurring Order Extension, see Public Release Note - Recurring Order Extension 1. Developers will get an overview of the feature's functionality, key components, and implementation details.
Name | Description |
---|---|
Recurring Order | A recurring order is an agreement to place the same order several times. When the initial order was placed successfully, the placement of all further orders is automatically triggered within the given time frame depending on the selected recurrence interval. A recurring order is legally considered to be a series of individual purchases, whereas a subscription is a single purchase for recurring delivery. |
Recurrence Information | Information about the recurrence:
*These information are mandatory. |
The basic idea behind implementing the recurring order feature is to attach recurrence information to an ordinary basket to mark it as recurring. The marked basket will then go through the standard checkout steps, including approval (if required).
The basket can be marked at any point during the checkout process, but due to limitations of payment methods (see Limitations) it is possible that the payment method already selected may be invalid. If the basket is marked as recurrent after the payment method has been selected, the basket validation will raise an error in case the payment method is invalid. The order creation process will then be interrupted due to the validation error.
If the order process was successful, the recurring basket will be given a new type and status to preserve it for the entire lifecycle of the recurring order. Afterwards the Recurring Order service is called to create a recurring order for this basket. The recurrence information and the resource to access the recurring basket are transmitted. The Recurring Order service only stores a reference to the basket and the according recurrence information (start date, interval etc) and calculates the next scheduled order date (initial: start date).
There is a global recurring order scheduling service job that determines all recurring orders with a next scheduled order date <= TODAY and executes a REST call to the stored order endpoint, which then calls the order creation for the recurring order.
When it is time to create an order, the Recurring Order service itself forwards the request to the basket service (ICM-AS) to create a new order for the recurring order. As mentioned before, the recurring basket is stored and will be used as blueprint for the new basket. More precisely, the recurring basket is cloned by the BasketCloneService. It is possible that the cloning process will result in a different basket in terms of items, prices, etc. because products may no longer be available or promotions may have expired and so on.
Therefore, the BasketCloneService is able to compare the two baskets (original and cloned) and collect and return the differences. So the result of the basket request to create a new basket for the recurring order will be a new (cloned) basket and possibly a list of differences between the two baskets.
The Recurring Order service provides an interface which allows to add a custom handler that evaluates these results and decides whether the basket can be ordered or not. If the check is successful, the Recurring Order service will call the basket service again to create an order. By default there is no such handler, so the check is skipped. If the order is successfully created, it is identified as an order from a recurring order and will be linked to it. This allows the customer to see which orders are part of a recurring order and which are one-off purchases.
Key concepts related to recurring orders are explained in the following sections. The entire workflow is explained in more detail and from a technical perspective in Implementation Details.
The recurring order feature can be switched on and off per channel.
Note
If the feature flag is switched off and recurring orders already exist, the UI will hide all related sections and no more orders will be created. Affected customers will not be notified.
See Cookbook - Recurring Orders | Recipe: Enable/Disable the Recurring Order Feature (Toggle) for further details.
If the feature is disabled:
All affected REST calls will return an error status 501 - Not implemented.
All information related to recurring orders will automatically disappear from all checkout pages in the storefront.
When creating a new recurring order, you can choose whether the prices of the included products are fixed or can change over time.
If the fixed price option is selected, only the product prices for all included products will be fixed throughout the lifecycle of the recurring order. However, the total price of the orders may change due to the expiry of promotions, changes in shipping costs, etc.
If the recurring order is created without the fixed price option, each basket will be calculated using the actual prices for the products at the time the basked was created. This means that the resulting orders may differ in cost.
The Recurring Order service can handle both scenarios. The fixed price flag is part of the internal REST API. ICM will transfer the flag based on the FixedPriceRecurringOrders
channel preference, which determines whether the fixed price option is active for all recurring orders from the channel or not.
See also Cookbook - Recurring Orders | Recipe: Set Fixed Product Prices for Recurring Orders to learn how to change this channel property.
After the basket cloning process, the newly created basket is recalculated, including the recalculation of promotions. This means:
All types of promotions are automatically applied on all orders created from recurring orders; there is currently no option to exclude them.
Initially applied promotions that become invalid or unavailable for the current customer in any way (expiration date, customer segment changes, application assignment, etc.) are automatically removed from the orders created. If the promotion becomes available again, it will automatically reappear.
Fixed price only refers to a fixed product price; but discounts from promotions will still affect the total price.
Recurring orders also count for categories such as "first order of the month".
The list of available payment methods for a recurring order can be restricted with a white list. This is necessary because orders out of recurring orders are created without any further user interaction. This way, only payment methods without redirect and with unlimited tenders are allowed. The basket validation will raise errors if an invalid payment method is selected for a recurring order. See Cookbook - Recurring Orders | Recipe: Adjust the Allowed Payment Methods to get information on how to define the list of available payment methods.
Depending on the selection of payment methods available and the method selected, the order creation process may fail due to incorrect payment information. In this case, you can optionally define a fallback payment method. The fallback payment method can be configured per channel and must not require any additional information or be user-specific such as bank accounts. Possible values include Invoice or Cash in Advance. If there is a fallback defined, the order creation will be triggered again with this fallback payment method. If no fallback is configured, the order creation will fail immediately.
The original payment method remains in the recurring order basket and is only replaced by the fallback payment method in the cloned baskets. If the original payment method becomes valid again for any reason, it will be used for all orders without a fallback.
This behavior can be changed, see Cookbook - Recurring Orders | Recipe: Configure Payment Fallback Method for details.
Orders created for a recurring order will use the invoice and shipping address selected in the original order. Addresses are referenced by ID (URN). This means that changes to an existing address are automatically applied to all future orders.
In this case, “change” means editing an existing address using the Edit button.
If a used address is deleted then a fallback mechanism will be applied.
The following fallbacks are used:
Invoice address:
Use preferred Invoice address (if it exists).
Use address if the customer has only one invoice address left.
Shipping address
Use preferred shipping address (if it exists).
Use preferred invoice address (if it exists and is also a shipping address).
Use address if the customer has only one shipping address left.
The fallbacks are applied to the given order. If no fallback can be applied, then no invoice or shipping address will be set. This will result in an incomplete recurring order, which will be automatically deactivated during the order creation process.
The scheduling of recurring orders is done by a scheduling job that is provided by the
It may be interesting to know when the next order will be created according to the regular schedule. Therefore, each time an order is created, it is calculated when the next order is due. If no order has been created yet or the recurring order has expired (number of orders or end date reached), there is no next order date. The date is stored in the database as SCHEDULEDORDERDATE
.
A notification is triggered on a successful order of a recurring order as well as on order creation errors. The notifications are implemented using Java extension points. The responsive storefront demo store includes an implementation of the Java extension point that sends an e-mail to the shop manager if a recurring order fails to be created.
It is possible to implement custom handlers that can be registered for one of the events (order success, order failed). See Cookbook - Recurring Orders | Recipe: Implement Notification for Failed/Successful Orders for technical details.
If no extensions are wired, only basic debug logging is performed.
If an order created from a recurring order is cancelled, it will not be removed (subtracted) from the number of orders and the number of desired repetitions for the recurring order.
Recurring orders can be set to inactive either by the customer via storefront / WebShop REST API or by the system when an error occurs. To distinguish between both cases, an additional database field ERRORCODE
contains an error code if the recurring order has been deactivated due to a system error. If this field is empty, it indicates that the customer deactivated the recurring order. The error code field is also exposed in the WebShop REST API and can be used to display an appropriate error message in the recurring order details in the My Account section. When a recurring order is reactivated after an error, the order creation process will be restarted. In the event of an error, the new error code will overwrite the old one and the recurring order will be deactivated again. However, if the order creation is successful, the old error code will be removed.
Recurring orders that have been set to inactive will no longer generate orders. When they are re-enabled, depending on the executeMissedOrders
flag, orders that were missed while the recurring order was inactive will or will not be caught up.
If no orders are caught up, the next order will be created on the next possible recurring date. The executeMissedOrders
flag is included in the recurrence information and is set when a basket is marked as recurrent. It is therefore possible to set this behavior per recurring order. The flag is optional. If no value is specified, the default value true
is used.
As the Recurring Order Extension is part of the icm-as infrastructure, it uses the same logging mechanism as the icm-as itself.
Before an order is placed, the Recurring Order Service checks whether the recurring order has expired. A recurring order is expired, when:
The order count has reached the number of allowed repetitions/occurrences, or
The end date has already passed
The recurring order is NOT expired, when:
No repetitions are set
The end date is today
When trying to place an order for an expired recurring order, the recurring order service will:
Return HTTP status 410 (Gone)
Stop placing orders for the recurring order
internal* = use Internal Service URI Resolvers to get URI of icm-as (Protocol, Host, Path etc. from ServerEnvironment.getInstance().getServerInfo())
icm-as already contains a Basket Service (providing recurring order basket operations via REST), a Basket Service Client (for REST communication with the Basket Service) and a Recurring Order Service Client (for REST communication with the Recurring Order Service, providing recurring order operations via REST). With ICM 12, the main part of the recurring order feature has been moved to the Recurring Order Extension. The recurring order parts of the external Webshop REST API are provided by the extension as well and are not available if the extension is not installed.
All parts, those in icm-as and those in the Recurring Order Extension, are explained in detail in the next section.
Technically, the feature toggle is implemented in icm-as as a database preference. The UI to adjust this in the back office is part of the Recurring Order Extension.
See Cookbook - Recurring Orders | Recipe: Enable/Disable the Recurring Order Feature (Toggle) for further details.
The Basket service provides (internal) REST API endpoints within icm-as to be used by other microservices/customizations/extensions:
Create a basket
Empty a basket
Clone an existing basket
Variable/fixed prices
Generate a list of differences between cloned and original basket
Get basket details
Edit recurrence information of a basket (change, remove)
Create an order
Recurring order or one-time purchase
Java extension points (in case of Success and Error order creation)
Delete a basket
Most of the functionality in this Basket service uses existing artifacts such as repositories and pipelines to fulfill the request. Only the ability to clone a basket was introduced for the recurring order feature.
This has been implemented in a separate BasketCloneService. A cloned basket may contain less data or may contain changed data, e.g., because products are no longer available or prices/promotions have changed.
The BasketDifferenceService collects all relevant fields that differ from the original basket to the cloned one. The differences are returned as a result of the cloning process and can be evaluated by the executor of the service.
See the next sections for more details.
BasketCloneService is an interface which is injected via Google Guice dependency injection to allow partners/customers to implement a custom solution.
The standard implementation in BasketCloneServiceImpl currently clones all required basket fields needed to order the basket. The following aspects are cloned:
Product line items
Addresses
Shipping method
Payment method
At the end of the cloning process the cloned basket is invalidated and recalculated.
Besides wiring a BasketCloneService there are two additional ways to adapt the functionality of the standard implementation.
The implementation class defines public methods for each aspect like line items, shipping etc. These methods could be overwritten by a custom solution that extends the standard implementation class to change the implementation.
If custom functionality should be added to the CloneHandler, a BasketCloneCustomHandler can be defined and wired. The custom code that needs to be added can be placed there.
BasketDifferenceService is an interface that is injected via Google Guice dependency injection to allow partners/customers to implement a custom solution.
The standard implementation in BasketDifferenceServiceImpl
currently only evaluates a reduced set of attributes:
Product line item count
Grand total gross
Grand total net
As already mentioned above, these attributes can be used by the NextOrderValidator in the Recurring Order Extension to decide whether an order can be created for recurring order or not.
The internal REST API of the Basket service provides the ability to:
Create a basket
Get Basket details
Edit a basket (limited to recurrence information only)
Delete a basket
Create an order for a basket
Here is a detailed overview:
Description | Method | URI | Arguments | Comments |
---|---|---|---|---|
Create a basket |
|
|
| Creates either an empty basket or clones an existing one. |
Get basket details |
|
| - | Returns details of the basket (currently only recurrence information) |
Edit a basket |
|
|
| Edit basket (currently only set/remove recurrence information) |
Delete a basket |
|
| - | Deletes the basket with the given basketID |
Creates an order for a basket |
|
| query parameter: | Creates an order for a basket identied by the given The parameter |
The standard WebShop REST API of icm-as provides the ability to mark an existing basket as recurrent. This is done by setting the recurrence information at the basket using the REST endpoint to partially update the basket (PATCH /baskets/<basketID>
). It is also possible to remove the recurrence information that marks the basket as recurrent. Also see Reference - Basket REST API 1.6.1.
If the Recurring Order Extension is not installed or the recurring order feature is not enabled, the REST endpoint will return an error.
Basket marked as recurrent | Ordered recurring order (basket) | (Cloned) Basket out of recurring order for next scheduled order | Placed order out of recurring order for scheduled order | |
---|---|---|---|---|
Basket status | 0 (Default) | 21 | 21 | 21 |
Basket type | 11 | 11 | 10 (Default) | 10 (Default) |
Additional fields | Custom attributes to hold recurrence information: | Custom attributes to hold recurrence information: | - | ID of the recurring Order saved as attribute: |
The Java class BasketConstants
(bts) defines constants for all used basket status and type values. These constants should be used instead of integer values.
Status:
0 - BasketConstants.BASKET_OPEN
21 - BasketConstants.BASKET_RECURRING
Type:
10 - BasketConstants.BASKETTYPE_REQUISITION
11 - BasketConstants.BASKETTYPE_RECURRING
There are two Business object extensions - one for BasketBO, one for OrderBO - to handle Recurring Order related topics.
BasketBORecurringExtension
Get and set recurrence information from/at basket
Delete recurrence
Ask if basket is recurring
Set status for original recurring order basket and cloned baskets
OrderBORecurringExtension
Ask if order is from recurring order
Get - and set recurring order ID from/at the order
The Recurring Order Service includes functions to:
Create a new recurring order based on given recurrence information
Get a list of recurring orders for a given owner
Enable/disable/delete recurring orders
Place a single order at the scheduled time for a recurring order
Placing a single order based on a recurring order calls the Basket service which is part of the icm-as to create a cloned instance of the stored recurring basket. After cloning the basket, a recalculation is triggered. The calculation may result in changes to the cloned basket, for example, products that are no longer available may be removed, product prices may have changed, promotions may have been exceeded, and so on.
Therefore, the clone method of the Basket service returns a list of relevant changes. Some changes may be acceptable to customers, others may not. The interface NextOrderValidator has been introduced to allow some custom implementation to do some validations on the returned basket differences. If the wired NextOrderValidator implementation returns a NextOrderValidationResult with isFailure() == true
, then order creation is skipped and the recurring order will be disabled (with an error code). Manual intervention is required to resolve this issue.
See the next sections for more details.
NextOrderValidator is an interface that is injected via Google Guice dependency injection to allow partners/customers to implement a custom solution. For the given input parameter BasketCreatedRO, which contains the calculated differences between the original and the cloned basket, an implementation of this interface can decide whether or not the cloned basket can be processed to create an order for the recurring order. Since all basic checks (are there line items, are the line items available etc.) are already done in the subsequent order creation process, there is currently no standard implementation of this interface. Partners/customers can implement a custom solution according to their needs. See Cookbook - Recurring Order | Recipe Implement a Custom Next Order Validator for further information on how to do this.
As mentioned before, ICM and the Recurring Order Service exclusively communicate via internal REST APIs. These REST APIs are described in detail in the following section.
Note
The repository identified by the repositoryID
as part of the URI path is not evaluated and is only still the URI part for compatibility reasons. Any non-empty string can be used.
Description | Method | URI | Params / Response | Comments |
---|---|---|---|---|
Add Recurring Order |
|
| Param:
| Adds a new recurring order with the information provided as parameter identified by given |
Get Recurring Order Details |
|
| Response: | Returns detailed information for the recurring order identified by the given |
Remove Recurring Order |
|
| Removes the recurring order identified by the given | |
Enables a Recurring Order |
|
| Enables a (disabled) recurring order identified by the given | |
Disables a Recurring Order |
|
| Disables a (enabled) recurring order identified by the given | |
Place order for a Recurring Order |
|
| Response: | Triggers the creation of a new order for a recurring order. |
Get Recurring Order List |
|
| Query Param:
Response:
| Returns the list of recurring orders for given owner. If no owner is provided, the entire list is returned. |
The Recurring Order Extension extends the standard WebShop REST API with a several REST endpoints to get, update (enable, disable) and delete recurring orders for private customers as well as for B2B customers and account admins. See also Reference - Recurring Order Extension REST API 2.0.0.
The Recurring Order Extension automatically creates a job Recurring Order Scheduling Service in the domain root and application system that is responsible for creating orders for each recurring order.
It can be found in the SMC Scheduling view. See also job description.
The schedule (run time) of the service defines when the recurring orders are triggered. In this example, every morning at 09.00 AM. Since recurring order schedule dates are stored without time information (only date), all orders that are due or overdue on that date will be generated.
The start and end dates of a recurring order do not contain time information. The schedule (run time) of the Recurring Order Schedule Service job defines when the recurring orders are triggered. If the job runs every day in the morning at 09.00 AM, all orders that are due or overdue on that date will be generated.
If there are a large number of recurring orders and it is expected that many orders will be generated each time the job is run, the maximum number of orders can optionally be specified to ensure that the system is not overloaded. If the number is specified, the job is aborted when the limit of orders is reached. If the job is then executed again at a later time, it will continue from that point.
To do this, the OrdersToBeProcessedMaxCount
parameter can be added in the attributes tab of the job. See example.
If the limit is used, it is recommended to run the job several times a day to ensure that all orders are generated at the desired time according to the selected interval.
As the Recurring Order Extension is part of the icm-as infrastructure, it uses the same persistence layer as the icm-as itself.
Currently there are limitations on the payment methods that can be used for recurring orders. This is necessary because orders out of recurring orders are created without any further user interaction triggered by a scheduling job. Therefore, only payment methods without redirect and with unlimited tenders are allowed.
The basket validation will raise errors if an invalid payment method is selected for a recurring order.
By default, each recurring order must be approved for B2B channels. This is done by a special order approval rule called SubscriptionApprovalRule
that is wired via the component framework. This recurring order approval rule can be adapted or removed to change the approval process. For more information refer to Cookbook - B2B Order Approval.
The recurring order feature does not currently work with the quoting functionality (see Concept - Quoting). This means that neither quotes can be included in a recurring order, nor can quotes contain anything recurring. This is true even if the product prices are fixed (see Fixed Prices).
There are multiple validators implemented to prevent that :
A cart can be marked as recurrent when a quote is already in the cart (1)
A quote is put into the basket when the cart is already marked as recurrent (2)
A cart that is marked as recurrent and accidentally contains a quote can be checked out (3)
(1) BasketRecurringValidator
(2) BasketQuoteValidator
(3) BasketValidationQuoteRecurringOrderHandler
If you want quoting and recurring orders to work together, you need to replace the above implementation with a customization.
The implementations for the validator interfaces (1) and (2) are bound using Google Guice, see Concept - Dependency Injection and ObjectGraphs for more information.
Validator (3) is included in the handler chain of the basket validation, which uses extension points to define the set of validation handlers to run, see Cookbook - Basket Validation for more information.