Broadleaf Microservices
  • v1.0.0-latest-prod

Upgrade or Downgrade Subscription

Upgrading or downgrading a subscription allows a customer to change their subscription to a different product level. For example, a customer might upgrade from a "Silver" subscription tier to a "Gold" subscription tier.

ModifySubscriptionHandler Implementations

The InitiateUpgradeSubscriptionHandler and InitiateDowngradeSubscriptionHandler manage the UPGRADE and DOWNGRADE action types respectively.

When a user initiates an upgrade or downgrade:

  • Validation:

    • Verifies that the requested UPGRADE or DOWNGRADE action is present in the subscription’s availableActions list.

    • Ensures that the selected upgrade/downgrade target products are valid configurations on the subscription’s root product.

    • For downgrades specifically, checks if the restrictDowngradeAfterDays threshold has been passed for the current billing cycle.

  • Cart Generation: Once validation is successful, the handler generates a new Cart populated with the new root subscription product and relevant items. During this generation, the AbstractGradeChangeSubscriptionModificationHandler performs a few unique operations:

    • Quantity Translation: It attempts to carry over the quantity from the original subscription, adjusting it if necessary to fall within the new product’s minimumThreshold and maximumThreshold boundaries.

    • Required Add-Ons: It evaluates the new product’s options to identify any required add-ons (options where minimumQuantity > 0). For any required add-on found, it automatically builds a dependent AddItemRequest and selects the default product or variant configured for that choice, ensuring the new cart is valid out of the gate.

    • It also injects attributes into the cart to coordinate the change, such as:

      • SUBSCRIPTION_ACTION_FLOW set to UPGRADE or DOWNGRADE.

      • SUBSCRIPTION_PRICE_EXPIRATION_DATE_UTC.

      • EXISTING_SUBSCRIPTION_DETAILS to help map line items back to their original subscription items.

        This generated cart is then returned in the ModifySubscriptionResponse.

UI Interaction & Flow

Surfacing the Action

In the My Subscriptions UI, a list of available actions is provided on the SubscriptionWithItems object. The UI can check if the UPGRADE or DOWNGRADE action is present in the availableActions list:

const isUpgradeAllowed = !!subscriptionWithItems.availableActions
  .find(action => action.actionType === DefaultSubscriptionActionType.UPGRADE)
const isDowngradeAllowed = !!subscriptionWithItems.availableActions
  .find(action => action.actionType === DefaultSubscriptionActionType.DOWNGRADE)

Initiating the Action

In the Broadleaf demo UI, when the upgrade/downgrade action is initiated, the user is navigated to an upgrade/downgrade selection page containing the available products that they can switch to.

These options are derived from the actionInfo map attached to the SubscriptionAction:

if (action?.actionInfo?.['upgradeOptions']) {
  let upgradeOptions: Array<string> = Array.isArray(action.actionInfo['upgradeOptions'])
    ? action.actionInfo['upgradeOptions']
    : [action.actionInfo['upgradeOptions'] as string];
    // Renders the options on the selection page
}
if (action?.actionInfo?.['downgradeOptions']) {
  let downgradeOptions: Array<string> = Array.isArray(action.actionInfo['downgradeOptions'])
    ? action.actionInfo['downgradeOptions']
    : [action.actionInfo['downgradeOptions'] as string];
    // Renders the options on the selection page
}

The upgrade-downgrade-option.tsx component is used to display each product option on the selection page. When an option is selected, the component triggers modifySubscription using the CustomerClient or AccountClient, passing the UPGRADE (or DOWNGRADE) action type along with the newItemRef set to the ID of the selected product option.

const response = await modifySubscription({
  subscriptionId: subscription.id,
  action: { actionType },
  newItemRefType: subscription.rootItemRefType,
  newItemRef: option.id,
});

Driving to the PDP

After the modifySubscription response is received, the Broadleaf demo UI extracts the newly generated cart ID and redirects the user to the Product Details Page (PDP) for the upgrade/downgrade product: /my-account/subscriptions/${subscription.id}/${actionType.toLowerCase()}/${option.id}?cart=${response.cart.id}

This allows the user to review the new product, select add-ons or options that apply to the upgraded/downgraded product tier, before proceeding to checkout.

Subscription Upgrade/Downgrade Pricing

During add-to-cart for a subscription grade change, the SubscriptionPricingService calculates the difference between the old and new product tiers.

Prepaid Upgrade Example: If a customer upgrades a Prepaid subscription mid-cycle:

  • The proratedAmount is calculated based on the new, higher-tier product from the action date to the next bill date.

  • The creditedAmount represents the already-paid value of the original lower-tier product from the action date to the next bill date.

  • The amountDueNow is the difference: proratedAmount - creditedAmount.

Prepaid Downgrade Example: If a customer downgrades a Prepaid subscription mid-cycle:

  • In most cases, downgrades are treated as Delayed Actions. The amountDueNow is $0, and the downgrade takes effect at the next billing cycle, preserving the higher-tier access the customer has already paid for.

Postpaid Upgrade Example: If a customer upgrades a Postpaid subscription mid-cycle:

  • The amountDueNow is $0.

  • The next invoice (estimatedFuturePayments) will reflect a priorUnbilledAmount for the lower-tier product from the start of the period to the action date, plus a proratedAmount for the new higher-tier product from the action date to the end of the period.

Postpaid Downgrade Example: If a customer downgrades a Postpaid subscription mid-cycle:

  • The amountDueNow is $0.

  • The next invoice (estimatedFuturePayments) will reflect a priorUnbilledAmount for the higher-tier product from the start of the period to the action date, plus a proratedAmount for the new lower-tier product from the action date to the end of the period.

Fulfillment Workflow Execution

When the user completes checkout for the modified subscription cart, the order is submitted. The subscriptionActionFlow on the order is identified as UPGRADE (or DOWNGRADE).

The system leverages the subscription’s fulfillmentWorkflow along with the UPGRADE or DOWNGRADE action flow to identify the correct fulfillment workflow to execute. See the following configuration to understand how the exact workflow is identified:

broadleaf:
  orderfulfillment:
    workflow:
      mapping:
        flows:
          match:
            fulfillmentTypeAUpgradeWorkflow:
              - fulfillmentTypeA-UPGRADE
            fulfillmentTypeADowngradeWorkflow:
              - fulfillmentTypeA-DOWNGRADE
            fulfillmentTypeBUpgradeWorkflow:
              - fulfillmentTypeB-UPGRADE
            fulfillmentTypeBDowngradeWorkflow:
              - fulfillmentTypeB-DOWNGRADE

Sample Configuration for an Upgrade Workflow:

broadleaf:
  workflow:
    client:
      flows:
        fulfillmentTypeAUpgradeWorkflow:
          description: Subscription Upgrade Workflow for Type-A Subscriptions
          historical-reset-enabled: true
          retry-enabled: true
      steps:
        fulfillmentTypeAUpgradeWorkflow:
          prepareSubscriptionFulfillmentActivity:
            admin-selectable: true
            decisions:
              ok: subscriptionModificationActivity
          subscriptionModificationActivity:
            admin-selectable: true
            decisions:
              ok: subscriptionBillingEventGenerationActivity
          subscriptionBillingEventGenerationActivity:
            admin-selectable: true
            decisions:
              ok: myCustomProvisioningActivityForFulfillmentTypeA
          myCustomProvisioningActivityForFulfillmentTypeA:
            admin-selectable: true
            decisions:
              ok: releaseSubscriptionLockActivity
          releaseSubscriptionLockActivity:
            admin-selectable: true
            decisions:
              ok: fulfillmentStatusFinalizationActivity
          fulfillmentStatusFinalizationActivity:
            admin-selectable: true

This workflow handles applying updates to the subscription, generating new billing events, and fulfilling the subscription updates.

Initial Workflow Context Parameters

  • Order.id

  • Order.submitDate

  • OrderFulfillment.id

  • Subscription.id

  • LockStatus.lockId (the subscription lock)

  • Application.id

  • Tenant.id

  • subscriptionActionFlow (UPGRADE or DOWNGRADE)

  • OrderFulfillment.fulfillmentWorkflow

  • customerTimezone (if applicable)

Workflow Activity Descriptions

  1. prepareSubscriptionFulfillmentActivity (broadleaf-subscription-operation-services-workflow) - Makes a production update to the subscription, setting Subscription#subscriptionStatus to FULFILLMENT_IN_PROCESS.

  2. subscriptionModificationActivity (broadleaf-subscription-operation-services-workflow) - Delegates to the EditUpgradeDowngradeSubscriptionModificationHandler. Makes a sandbox update to the subscription, replacing the previous configuration and subscription items with the newly configured subscription items from the upgrade/downgrade cart. It also applies any updated pricing to the subscription and its items, and sets the subscription status back to ACTIVE.

  3. subscriptionBillingEventGenerationActivity (broadleaf-subscription-operation-services-workflow) - Delegates to the SubscriptionModificationBillingEventGenerationHandler. Generates new BillingEvents in the sandbox for the subscription based on the new items and prices.

  4. myCustomProvisioningActivityForFulfillmentTypeA - An example of a custom workflow activity that might be executed for a specific fulfillment type to provision a service.

  5. releaseSubscriptionLockActivity (broadleaf-subscription-operation-services-workflow) - The subscription lock is released and all subscription sandbox updates are applied to the production record.

  6. fulfillmentStatusFinalizationActivity (broadleaf-order-operation-services-workflow) - Updates the OrderFulfillment status to indicate that fulfillment has completed (e.g., setting it to FULFILLED).