Concept - Recurring Orders

Introduction

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.

Glossary

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:

  • Start-Date*

  • End-Date

  • Interval* (every week, every 2 months etc.)

  • Repetitions (number repetitions/occurrences)

*These information are mandatory.

References

General Workflow

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.

Feature Toggle

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.

Fixed Prices

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.

Promotions

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".

Payment Methods

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.

Addresses

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:

    1. Use preferred Invoice address (if it exists).

    2. Use address if the customer has only one invoice address left.

  • Shipping address

    1. Use preferred shipping address (if it exists).

    2. Use preferred invoice address (if it exists and is also a shipping address).

    3. 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.

Scheduling

The scheduling of recurring orders is done by a scheduling job that is provided by the Recurring Order Extension. See Scheduling Service Job for further details.

Next Order Date

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.

Notifications

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.

Order Count

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.

Inactive Recurring Orders

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.

Logging

As the Recurring Order Extension is part of the icm-as infrastructure, it uses the same logging mechanism as the icm-as itself.

Workflows

Mark Basket as Recurrent

Create Recurring Order

Place an Order

Get List of Recurring Orders

Get Recurring Order Details

Remove/Deactivate a Recurring Order

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

Implementation Details

Technical Overview

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.

ICM-AS

Feature Toggle

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.

Basket Service

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

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

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.

Internal REST API

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

POST

/sites/{siteID}/applications/{applicationID}/baskets

com.intershop.component.basket.capi.remote.service.entities.CreateBasketSO

Creates either an empty basket or clones an existing one.
When cloning, you can decide whether to generate basket differences and whether products should have fixed prices.

Get basket details

GET

/sites/{siteID}/applications/{applicationID}/baskets/{basketID}

-

Returns details of the basket (currently only recurrence information)

Edit a basket

PUT

/sites/{siteID}/applications/{applicationID}/baskets/{basketID}

com.intershop.component.basket.capi.remote.service.entities.BasketSO

Edit basket (currently only set/remove recurrence information)

Delete a basket

DELETE

/sites/{siteID}/applications/{applicationID}/baskets/{basketID}

-

Deletes the basket with the given basketID

Creates an order for a basket

POST

/sites/{siteID}/applications/{applicationID}/baskets/{basketID}/orders

query parameter:
String basketID
String recurringOrderID
String recurringOrderNo

Creates an order for a basket identied by the given basketID.
Optional: Assigns an order to a recurring order when recurringOrderID is provided as query parameter.

The parameter recurringOrderNo is only used for customer notification

External WebShop REST API

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.

image-20241107-160304.png

Basket Status & Type

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:
Date: recurrenceStartDate
Date: recurrenceEndDate
String: recurrenceInterval
Integer: recurrenceRepetitions

Custom attributes to hold recurrence information:
Date: recurrenceStartDate
Date: recurrenceEndDate
String: recurrenceInterval
Integer: recurrenceRepetitions

 -

ID of the recurring Order saved as attribute:
OrderPO:recurringOrderID

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

Business Object Extensions

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

Recurring Order Extension

Recurring Order Service

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

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.

Internal REST API

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

PUT

/repositories/{repositoryID}/recurringorders/{externalID}

Param:

com.intershop.component.recurringorder.capi.remote.service.entities.RecurringOrderSO

Adds a new recurring order with the information provided as parameter identified by given externalID

Get Recurring Order Details

GET

/repositories/{repositoryID}/recurringorders/{externalID}

Response:
com.intershop.component.recurringorder.capi.remote.service.entities.RecurringOrderSO

Returns detailed information for the recurring order identified by the given externalID.

Remove Recurring Order

DELETE

/repositories/{repositoryID}/recurringorders/{externalID}

Removes the recurring order identified by the given externalID.

Enables a Recurring Order

POST

/repositories/{repositoryID}/recurringorders/{externalID}/enable

Enables a (disabled) recurring order identified by the given externalID.

Disables a Recurring Order

POST

/repositories/{repositoryID}/recurringorders/{externalID}/disable

Disables a (enabled) recurring order identified by the given externalID.

Place order for a Recurring Order

POST

/repositories/{repositoryID}/recurringorders/{externalID}/orders

Response:
com.intershop.component.recurringorder.capi.remote.client.entities.BasketCreatedRO or Errors

Triggers the creation of a new order for a recurring order.

Get Recurring Order List

GET

/repositories/{repositoryID}/recurringorders

Query Param:

String owner (optional)

Response:

com.intershop.component.recurringorder.capi.remote.service.entities.RecurringOrderListSO

Returns the list of recurring orders for given owner. If no owner is provided, the entire list is returned.

Extension of External WebShop REST API

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.

image-20241107-161244.png

Scheduling Service Job

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.

Order Creation Date/Time

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.

Limit - Number of Orders

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.

Persistence

As the Recurring Order Extension is part of the icm-as infrastructure, it uses the same persistence layer as the icm-as itself.

Limitations

Restrictions on Payment Methods

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.

B2B Approval

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.

Incompatibility with Quoting Feature

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.

Disclaimer
The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.
The Intershop Knowledge Portal uses only technically necessary cookies. We do not track visitors or have visitors tracked by 3rd parties. Please find further information on privacy in the Intershop Privacy Policy and Legal Notice.
Home
Knowledge Base
Product Releases
Log on to continue
This Knowledge Base document is reserved for registered customers.
Log on with your Intershop Entra ID to continue.
Write an email to supportadmin@intershop.de if you experience login issues,
or if you want to register as customer.