Broadleaf Microservices
  • v1.0.0-latest-prod

Product & Pricing Setup

This document is all about setting up backend data in preparation for selling subscription products. This includes catalog configuration, pricing configuration, and offer configuration.

Catalog & Product Configuration

Broadleaf added out-of-the-box domain support for subscription-related settings and fulfillment workflows. Subscription products are intended to be based on STANDARD & MERCHANDISING_PRODUCT-typed Products.

Product Setup

The Product entity has been enhanced with several fields to support subscription behavior.

isSubscription flag

Marks the product as a subscription, enabling subscription-specific logic and UI options.

subscription isSubscription flag
subscription isSubscription flag 2

When the isSubscription flag is enabled, the following subscription-related fields are revealed:

Subscription Settings

subscription subscription settings
  • maxNumberOfActiveSubscriptions - Limits the maximum number of active subscriptions a customer can hold for this product.

  • allowQtyEditAfterInitialPurchase - Determines whether the quantity of this subscription can be edited after the initial purchase. (Defaults to false)

  • restrictDowngradeAfterDays - Specifies the number of days after which downgrading the subscription is restricted. This restriction resets at the start of a new billing cycle.

  • allowChangingBillingFrequency - Determines whether the customer can change the billing frequency after the initial purchase. (Defaults to false)

  • cancellationPolicyRef - A reference to the cancellation policy that dictates how this subscription can be cancelled.

  • upgradeProductIds - References the IDs of the products this subscription can be upgraded to.

  • downgradeProductIds - References the IDs of the products this subscription can be downgraded to.

  • autoRenewalEnabled - Indicates whether this subscription should auto-renew by default.

  • allowAutoRenewalModification - Indicates whether the customer can opt in and out of auto-renewal.

Fulfillment Workflow

Indicates a specific workflow to route this product to for fulfillment concerns (e.g., a subscription provisioning flow).

subscription fulfillment workflow

The fulfillment workflow uses a DataDrivenEnum lookup.

subscription data driven enums

ProductTerm Setup

The ProductTerm entity has also been enhanced for subscription terms:

  • endOfTermStrategy - Defines the strategy to use at the end of the term (e.g., AUTO_RENEW, CANCEL).

  • allowEndOfTermStrategyModification - Indicates whether the customer can modify the end-of-term strategy.

Product Choice / Add-on Setup

The ItemChoice entity (used for add-ons) has been enhanced to support subscription add-ons.

Regardless of what the parent product’s isSubscription flag is set to, an add-on can still be a subscription add-on.

subscription product choice

The add-on’s isSubscription flag will also show/hide the subscription-related settings.

subscription add on
subscription edit rules
  • isSubscription - Marks the add-on itself as a subscription. (Defaults to false)

  • isSeparateFromPrimaryItem - Indicates if this add-on should be managed as a separate line item from its parent. (Defaults to false)

  • hasPostPurchaseEditRules - Enables the evaluation of post-purchase edit rules for this add-on. (Defaults to false)

  • allowEditAfterInitialPurchase - Allows general editing of the add-on after the initial purchase. (Defaults to false)

  • allowQtyEditAfterInitialPurchase - Allows the quantity of the add-on to be edited after the initial purchase. (Defaults to false)

  • allowAddAfterInitialPurchase - Allows this add-on to be added to an existing subscription after the initial purchase. (Defaults to false)

  • allowRemoveAfterInitialPurchase - Allows this add-on to be removed from an existing subscription after the initial purchase. (Defaults to false)

  • restrictQtyDecreaseAfterDays - Specifies the number of days after which decreasing the quantity of the add-on is restricted.

  • restrictRemovalAfterDays - Specifies the number of days after which removing the add-on is restricted.

Note
The individual post-purchase edit rules are shown/hidden based on the hasPostPurchaseEditRules flag. The post-purchase edit rules (e.g. allow edit after initial purchase) are set back to false & null (for the integer fields) when either the hasPostPurchaseEditRules flag or the isSubscription flag is set to false.

Pricing & Fee Configuration

Term and Frequency Based Pricing

Broadleaf added framework support to allow term-based pricing with multiple frequencies.

Product Pricing Configuration

To configure a product to have term-based pricing, you can add the available terms in the Advanced Pricing → Advanced Pricing section. For example, one of our sample products has 1-Month, 1-Year, and 2-Year terms:

subscription term based pricing

These terms configurations will help drive different frequency options in the storefront to retrieve specific pricing.

PriceData can be added to specify the pricing for each term and frequency combination, which can be done in the same Advanced Pricing section in the Product management page (see below) or in the PriceList management page (see later section):

subscription product price data
Note
There can only be one unique PriceData per PriceList for the same target, term, and frequency combination.

PriceList Configuration

The term-based pricing needs to be added to a PriceList, which can be managed in Pricing → Price Lists section. In here, you can add as many PriceLists as needed to align with your pricing scopes/variations.

PriceData Setup

Each PriceData specifies the price for a unique target, term, and frequency combination.

Once the target is specified, the pricing-related details are required. First, the payment strategy must be specified, which can be ONE_TIME, PREPAID, or POSTPAID, each option driving different configuration requirements.

subscription prepaid v postpaid

Once a payment strategy is selected, take POSTPAID as an example, you can then specify the recurring price, frequency, and term:

subscription postpaid
subscription term duration
Sample PriceData

As part of our demo data, we added some example term-based pricing for the VPN Products:

The VPN Plan Pricing PriceList (Euro) has:

  • VPN Basic:

    • 1 month term, monthly: 10

    • 1 year

      • monthly: 5

      • annually: 50 (2 months free)

    • 2 year

      • monthly: 2.5

      • annually: 25 (2 months free)

  • VPN Plus:

    • 1 month term, monthly: 20

    • 1 year

      • monthly: 10

      • annually: 100 (2 months free)

    • 2 year

      • monthly: 5

      • annually: 50 (2 months free)

  • VPN Ultimate:

    • 1 month term, monthly: 30

    • 1 year

      • monthly: 15

      • annually: 150 (2 months free)

    • 2 year

      • monthly: 7.5

      • annually: 75 (2 months free)

Pricing Targets

When requesting prices for a target (e.g. SKU), the system will return the best matched PriceData for the target. The system evaluates all PriceData that exactly match the target’s requested characteristics, term, frequency, and payment strategy.

If multiple matching PriceData are available (e.g. from different PriceLists), they are prioritized by the following criteria (in order):

  • Currency: The PriceData that matches the requested currency wins

  • Priority: Lowest numeric value first (e.g. 1 is higher priority than 10)

  • If the prices are still equivalent, it falls back to PriceData#PRICE_DATA_COMPARATOR:

    • Vendor Reference: Nulls last

    • Terms: Nulls last

      • Note: At this point of the logic, the terms are equivalent

    • Number of Characteristics: More is better (reverse order)

      • Note: At this point of the logic, the characteristics are equivalent

    • Upfront Price: Lowest first

    • Recurring Price: Lowest first

    • Payment Strategy: ONE_TIME > POSTPAID > PREPAID

      • Note: At this point of the logic, the payment strategies are equivalent

    • Usage Price: Lowest first

You can find the exact matching logic in com.broadleafcommerce.pricing.service.DefaultPriceInfoService#putPricesForType and com.broadleafcommerce.pricing.service.DefaultPriceInfoService#getBestPriceFromPriceLists.

Requesting All Frequencies

The /browse/price-targets endpoint also supports requesting all the available frequencies based on the given term, by specifying getAllAvailableFrequencies to true in the X-Price-Info-Context header.

For example, if we request all the available frequencies for the VPN-BASIC target (see the Sample PriceData section for the setup) with the following cURL request, we would get the PriceData for both 1 Monthly and 1 Annually frequencies for the 2-Year term:

curl --location 'https://sample.localhost:8456/api/catalog-browse/browse/price-targets' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Origin: https://sample.localhost:8456' \
--header 'X-Application-Token: SAMPLE' \
--header 'X-Price-Context: {"locale":"en-US","currency":"EUR"}' \
--header 'X-Price-Info-Context: {"getAllAvailableFrequencies": true}' \
--data '{
    "targetsMap": {
        "VPN-EXTREME": [
            {
                "targetType": "SKU",
                "targetId": "VPN-BASIC",
                "priceableFields": {
                    "basePrice": 10
                },
                "termDurationLength": 2,
                "termDurationType": "YEARS"
            }
        ]
    }
}'

Subscription Offer Configuration

Begin & End Period

  • Only relevant for subscriptions. These periods determine when the adjustments are applicable to the subscription.

  • Note that these periods align with the Broadleaf Subscription Periods.

  • For example, for an "All Products 50% off for the first 3 months for monthly subscriptions" offer, the begin period would be 1 and the end period would be 3, representing that the offer is applicable for month 1, 2, and 3.

Apply Offer to a Dependent Item (Add-on)

When trying to apply an offer to dependent items (add-ons), there are some notable configurations required:

  • Configure the root product (parent item):

    • In the Product Options section, edit the addOn product. Then in the Advanced Pricing section, ensure Discount Allowed is set as YES for the add-on/itemChoice.

  • Configure the offer:

    • Offer Target Type is Order Item.

    • Target Item Criteria includes the add-on item that is to be discounted.

    • In the Advanced section, the Apply discount to dependent items such as add-ons? is set as YES.

After this setup, the offer can be applied to the add-on:

subscription add on targeting offer

Automatic Removal of Adjustments Due to Qualifier Cancelled

Let’s say you have a "Buy 1 Subscription A Get 1 Subscription B Free" offer, and a customer signed up for both subscriptions. If Subscription A is cancelled, then the adjustment is removed from Subscription B automatically.

The overall flow looks like:

  • When the customer initiates cancel, a list of PendingSubscriptionItemAdjustmentRemoval objects are stored within the cancel cart’s internal attribute with the key "pendingSubscriptionItemAdjustmentRemovalDetails", which can be used to show in the front-end to convey what adjustments will be removed as part of the cancellation.

  • Once the cancellation cart is submitted, the removeAdjustmentForCancelledQualifierActivity workflow activity will be executed as part of the cancellation workflow, which will:

    • Do nothing if it’s a cancellation but the cancellation policy is CANCEL_AUTO_RENEWAL.

    • If it’s a cancellation and the policy is IMMEDIATE_CANCELLATION, it will first remove the subscription item adjustments, then generate billing events to account for the removed adjustments.

Relevant Components

  • InitiateCancelSubscriptionHandler.buildPendingSubscriptionItemAdjustmentRemovalDetails

  • PendingSubscriptionItemAdjustmentRemoval

  • DefaultRemoveAdjustmentForCancelledQualifierActivity

  • SystemRemoveSubscriptionAdjustmentBillingEventGenerationHandler

Testing Steps

  • Setup the offer with the relevant qualifier and target

  • Add both subscriptions in the same cart so that the offer will be applied

  • Submit cart for checkout

  • Once submitted, you can see the SubscriptionItemAdjustment on the target item

  • Cancel the qualifier subscription

  • Once submitted, you can see the SubscriptionItemAdjustment for the target item is removed and new billing events are generated to reflect the loss of adjustment

  • Reactivate the qualifier subscription

  • Once submitted, you can see the SubscriptionItemAdjustment is added back to the target item and new billing events are generated to reflect the addition of the adjustment

CSR Add Adjustments to Subscriptions

As an admin, you can also manually add an adjustment to an existing subscription. This can be done by clicking "Add Adjustment to Subscription" in the subscription detail view, then you can configure how the adjustment should be applied:

subscription csr add adjustment
subscription csr add adjustment 2

IMPORTANT: It is important to know that the offer criteria (e.g. qualifier & target rules, min/max subtotal, etc.) is NOT evaluated when going through this flow. Even if the offer wouldn’t have applied to the subscription under normal circumstances, the offer will still be forcibly applied when manually adding the adjustment.

Relevant Components

  • SubscriptionOperationEndpoint.addAdjustmentToSubscription

  • SubscriptionAddAdjustmentModificationHandler

  • DefaultSubscriptionOfferService

  • CsrAddAdjustmentBillingEventGenerationHandler

Testing Steps

  • Sign up for a subscription (with or without offer applied)

  • In the admin, go to the subscription details

  • Click "Add Adjustment to Subscription"

  • Configure how the adjustment should be applied

  • Once submitted, you can see the SubscriptionItemAdjustment is added to the subscription item and new billing events are generated to reflect the addition of the adjustment

Advanced Subscription Offer Targeting Features

Offer Targeting a Subscription Flow

Offers can now explicitly target a specific subscription flow (such as SUBSCRIPTION_CREATE, SUBSCRIPTION_EDIT, SUBSCRIPTION_UPGRADE, or SUBSCRIPTION_DOWNGRADE) or be applicable to ANY flow using the flow property on the Offer. The DefaultOfferCandidateService will filter out offers that do not match the current order flow, unless it is an existing offer that is already applied to the subscription (for example, retaining an existing discount when upgrading).

First Time Subscriber Offers

New fields activeSubscriptionRule and formerSubscriptionRule on SubscriptionDiscount allow limiting offers based on a user’s active and former subscriptions, effectively enabling "first time subscriber" offers. DefaultOfferCandidateService#filterOffersByOrderSubscriptionState evaluates these rules during offer processing to verify that the user has no existing subscriptions that would disqualify them from the offer.

Maintaining Free Trial After Subscription Modification

For post-purchase subscription flows (like upgrades or edits), existing free trials or other subscription discounts are maintained. The ExistingOfferRef tracks how many periods have already been used, and DefaultOfferCandidateService (initializeSubscriptionRelatedOfferBasedOnExistingSubscription) accurately adjusts the new beginPeriod and endPeriod on the candidate offer to retain the remaining discount periods based on the current period of the subscription.

Offer Targeting by PERIOD_FREQUENCY and PERIOD_TYPE

The rule builder for order-item criteria has been enhanced to support targeting by recurringPrice?.periodFrequency and recurringPrice?.periodType. This allows offers to target subscriptions based on specific billing frequencies (e.g., Monthly vs. Annually) using the DefaultRecurringPeriodType enum.

Subscription Bundles

Broadleaf supports bundling multiple subscription products together using the MERCHANDISING_PRODUCT product type. This allows you to sell a package of distinct subscription services under a single unified offering, ensuring they share the same subscription lifecycle (i.e. billing frequency, payment strategy, and terms).

Example: Premium Streaming & Internet Service Bundle

Consider a scenario where you want to sell a "Premium Streaming & Internet Service Bundle" that includes:

  • Premium Streaming Service (Product A)

  • Fiber Internet Service (Product B)

Instead of the customer adding these items individually to the cart, the Bundle guarantees they are purchased together with synchronized billing & terms.

Catalog Configuration

The bundle itself is created as a Product with the productType set to MERCHANDISING_PRODUCT.

Key configuration points for the Merchandising Product:

  • isSubscription Flag: Set to true. This marks the bundle itself as a subscription.

  • Payment Strategy: The paymentStrategy (e.g., PREPAID or POSTPAID) is explicitly defined on the merchandising product. This is crucial because the bundle itself is not directly priced via PriceData; instead, its price is the sum of its parts. Declaring the payment strategy here ensures all child items conform to the bundle’s billing model.

  • Fulfillment Workflow: You can specify a dedicated fulfillmentWorkflow on the bundle to route the entire package through a unified provisioning process instead of fulfilling the items separately.

  • Product Terms: The valid terms for the bundle (e.g., 2 Years) are defined on the merchandising product.

Bundle Items (Item Choices)

The actual services within the bundle are linked using ProductOption`s of type `ITEM_CHOICE.

For the Premium Streaming & Internet Service Bundle, you would configure two Item Choices:

  1. Streaming Service Choice: Targets the "Premium Streaming Service" product with a minimumQuantity of 1.

  2. Internet Service Choice: Targets the "Fiber Internet Service" product with a minimumQuantity of 1.

These item choices guarantee that when the bundle is added to the cart, the underlying services are automatically added as dependent cart items.

Pricing Configuration

Because the bundle is a MERCHANDISING_PRODUCT, it does not have its own standalone PriceData with a base or recurring price. Its price is derived entirely from the dependent items.

When pricing the bundle, the system evaluates the PriceData for the underlying services ("Premium Streaming Service" and "Fiber Internet Service") using the terms and frequency selected for the bundle. During cart hydration, the DefaultCartItemCatalogInformationService explicitly overrides the paymentStrategy of the child items with the bundle’s paymentStrategy to ensure cohesive billing.