New in Chargebee: Explore Reveal and understand your payment performance end-to-end.Try Now
Docschargebee docs
HomeBillingCPQPaymentsRevRecGrowthReveal
Support

Product Updates


  • Release Notes

Getting Started


  • Overview
  • Chargebee Billing Data Centers
  • Object Relationship Model
  • Understanding Sites
  • Chargebee Tech Glossary
  • Articles and FAQ

Implementing Chargebee


  • Implementation Guide
  • Go-live Checklist
  • Articles and FAQ

Agentic AI


  • Chargebee Copilot
  • Catalog Setup Assistant
  • MCP Servers

Developer Resources


  • Developer Resources Overview
  • API Explorer
  • Articles and FAQ

Chargebee Apps


  • Chargebee Apps CLI Developer Guide

Product Catalog


  • Product Catalog Overview
  • Coupons
  • Articles and FAQ

Subscriptions


  • Working with Subscriptions
  • Billing
  • Orders
  • Articles and FAQ

Customers


  • Managing Customers
  • Account Hierarchy
  • Email Notifications
  • Branding
  • Configure Multiple Languages
  • Articles and FAQ

Entitlements


  • Entitlements Overview
  • Features Overview
  • Feature Management
  • Managing Product Entitlements
  • Subscription Entitlements
  • Customer Entitlements
  • Grandfathering Entitlements
  • Articles and FAQ

Usage Based Billing


  • Understanding Usages
  • Setting up Usage Based Billing
  • Usage Alerts
  • Prepaid credits

Invoices and Credit Notes


  • Invoices
  • Credit Notes
  • Quotes [Legacy]
  • Transactions
  • Articles and FAQ

Taxes


  • Overview
  • Configuring Taxes
  • Country-specific Taxes
  • Articles and FAQ

Hosted Capabilities


  • Overview
  • Hosted Checkout
  • Hosted Self-Serve Portal
    • Integration Options
    • Chargebee Self-Serve Portal
    • Portal Custom Code
      • Fetch Allowed Plan Configuration
      • Retrieve Subscription Change Timeline
    • Chargebee Widgets
  • Hosted Pages Features
  • Additional Hosted Pages
  • Payment Components
  • Pricing Table
  • Mobile SDKs and Wrappers
  • Articles and FAQ

Site Configuration


  • Users & Roles
  • Custom Fields & Metadata
  • Approvals
  • Mandatory Fields
  • File Attachments & Comments
  • Advanced Filter Options
  • Multicurrency Pricing
  • Multi-decimal Support
  • Configuring Reason Codes
  • Events and Webhooks
  • API Keys
  • Time Zone
  • Time Machine
  • Transfer Configurations
  • Articles and FAQ

Multi Business Entity


  • Multi Business Entity Overview
  • Customer Transfer Overview
  • Articles and FAQ

Mobile Subscriptions


  • Overview
  • Omnichannel Subscriptions
  • Omnichannel Subscriptions (Legacy)

Reports and Analytics


  • RevenueStory
  • Home Dashboard
  • Frequently Asked Questions
  • FAQs for Classic Reports Sunset
  • Articles and FAQ

Integrations


  • Sales
  • Customer Support and Success
  • Finance
  • Tax
  • eInvoicing
  • Marketing
  • Stitch
  • Collaboration
  • Contract Management
  • Ecommerce Management
  • Articles and FAQ

Data Privacy & Security


  • Two Factor Authentication
  • SAML Single Sign-On
  • System for Cross-Domain Identity Management (SCIM)
  • EU-GDPR
  • Consent Management
  • Personal Data Management
  • Compliance Certificates
  • HIPAA Guidelines
  • PCI Recommendations and Integration Types
  • Articles and FAQ

Data Operations


  • Bulk Operations
  • Migration
  • Articles and FAQ
  1. Billing
  2. Hosted Capabilities
  3. Hosted Self-Serve Portal
  4. Portal Custom Code
  5. Retrieve Subscription Change Timeline
  1. Billing
  2. Hosted Capabilities
  3. Hosted Self-Serve Portal
  4. Portal Custom Code
  5. Retrieve Subscription Change Timeline

Retrieve subscription change timeline

Use the fetchChangeSubscriptionOptions function to control when a subscription change made in the Self-Serve Portal takes effect: either immediately or at the end of the current term. Chargebee invokes this function when a customer changes their subscription and uses the value you return to schedule the change.

This function is one of the two functions supported by Portal Custom Code.

Usage and invocation

Write your business logic to classify each subscription change, and return end_of_term to tell Chargebee when to apply it:

  • Set end_of_term to false to apply the change immediately.
  • Set end_of_term to true to apply the change at the end of the term.

Example

Apply upgrades immediately so the customer gets the added value right away, and defer downgrades to the end of the term so the customer keeps what they paid for until the term ends.

Warning

If multi-decimal support is enabled for your site, read the plan quantity from subscription_changes.plan_quantity_in_decimal and each addon quantity from subscription_changes.addons[].quantity_in_decimal. On these sites, the portal sends the quantity in the *_in_decimal fields, and plan_quantity is null. Custom code that reads plan_quantity receives null and applies the change incorrectly.

These *_in_decimal fields are strings, so wrap them in Number(...) before you compare them.

Requirements

  • Write the function in JavaScript.
  • Make sure the function runs in a Node.js v6.0 environment.
  • Use the resources that Chargebee passes to the function to write your business logic. These resources use the same format as the output of the Node.js client library.
  • To retrieve additional resources, use Chargebee's Node.js client library, provided you've already configured it with your API key and secret key.
  • Return a JavaScript object that matches the JSON schema on this page.

Parameters

ParameterTypeDescription
dataObjectThe resources that Chargebee passes to your custom code.
data.subscriptionObjectThe subscription object.
data.subscription_changesObjectThe updated plan and addons selected by the customer for the subscription.
data.subscription_changes.plan_idString, requiredItem price ID of the plan.
data.subscription_changes.plan_quantityIntegerQuantity of the plan. When multi-decimal support is enabled for your site, this field is null; read plan_quantity_in_decimal instead.
data.subscription_changes.plan_quantity_in_decimalStringQuantity of the plan as a decimal string. Provided when multi-decimal support is enabled for your site. Use this field instead of plan_quantity on multi-decimal sites, and wrap it in Number() before comparing.
data.subscription_changes.addonsArray<Object>List of addons.
data.subscription_changes.addons[].idString, requiredItem price ID of the addon.
data.subscription_changes.addons[].quantityIntegerQuantity of the addon. When multi-decimal support is enabled for your site, this field is null; read quantity_in_decimal instead.
data.subscription_changes.addons[].quantity_in_decimalStringQuantity of the addon as a decimal string. Provided when multi-decimal support is enabled for your site. Wrap it in Number() before comparing.
data.customerObjectThe customer object.
data.item_pricesArray&lt;Object&gt;The plan and addon item prices currently part of the subscription.
callbackFunctionFunction to send the output of the custom code to Chargebee.
callback.errorObjectError details to be sent to Chargebee when the custom code has not been successfully executed.
callback.successObjectOutput of the custom code to be sent to Chargebee when the custom code has been successfully executed.
loggerFunctionHelps log debug information for your custom code. To emit a log entry, call logger.debug('key', 'value') in your custom code. The logs appear in Chargebee Billing on the Custom Codes page at https://YOUR_SUBDOMAIN.chargebee.com/custom_codes, under the Execution section, after you execute the custom code.

Example

This function treats a change as a downgrade when the customer reduces the quantity of the current plan or moves to a lower-priced plan, and defers those downgrades to the end of the term. All other changes apply immediately.

var chargebee = require("chargebee")

exports.fetchChangeSubscriptionOptions = function ({ subscription, subscription_changes, item_prices }, callback, logger) {

    try {
        const changeSubPlanItemPrice = item_prices.find(({ item_price }) => item_price.id === subscription_changes.plan_id);
        const subPlanItemPrice = subscription.subscription_items.find(subItem => subItem.item_type === 'plan')
        const subPlanId = subPlanItemPrice.item_price_id;

        let downgrade = false;
        // On multi-decimal sites, the quantity is in the *_in_decimal fields; fall back to the integer fields otherwise.
        let changedPlanQuantity = Number(subscription_changes.plan_quantity_in_decimal || subscription_changes.plan_quantity);
        let currentPlanQuantity = Number(subPlanItemPrice.quantity_in_decimal || subPlanItemPrice.quantity);
        let changedPlanUnitPrice = changeSubPlanItemPrice.item_price.price;

        // Same plan, quantity reduced.
        if (subPlanId == subscription_changes.plan_id && changedPlanQuantity < currentPlanQuantity) {
            downgrade = true;
        }
        // Different plan at a lower price.
        if (subPlanId != subscription_changes.plan_id && subPlanItemPrice.unit_price > changedPlanUnitPrice) {
            downgrade = true;
        }

        callback(null, { end_of_term: downgrade })
    } catch (err) {
        callback(err);
    }

}
Sample output
{
  "end_of_term": true
}

Apply upgrades immediately and downgrades at the end of the term

The basic example inspects the plan only. This version also treats a change as a downgrade when the customer reduces an addon quantity or removes an addon. Any of these changes is deferred to the end of the term; otherwise, the change applies immediately. Use this approach when your subscriptions include quantity-based addons.

Custom code
let chargebee = require("chargebee");

exports.fetchChangeSubscriptionOptions = function ({ subscription, subscription_changes, item_prices }, callback, logger) {

    try {

        // On multi-decimal sites, quantities are in the *_in_decimal fields; fall back to the integer fields otherwise.
        const qty = (item) => Number(item.quantity_in_decimal || item.quantity);

        // Current plan item on the subscription.
        const subPlanItemPrice = subscription.subscription_items.find(subItem => subItem.item_type === 'plan');
        // Addons currently on the subscription.
        const addonsBefore = subscription.subscription_items.filter(item => item.item_type === 'addon');
        // Addons selected in the change.
        const addonsAfter = subscription_changes.addons || [];

        let downgrade = false;

        // Same plan, quantity reduced.
        if (Number(subscription_changes.plan_quantity_in_decimal || subscription_changes.plan_quantity) < qty(subPlanItemPrice)) {
            downgrade = true;
        }

        // Any addon removed or reduced in quantity.
        for (const before of addonsBefore) {
            const after = addonsAfter.find(a => a.id === before.item_price_id);

            if (!after || qty(after) < qty(before)) {
                downgrade = true;
                break;
            }
        }

        callback(null, { end_of_term: downgrade });
    } catch (err) {
        callback(err);
    }

}
Sample output
{
  "end_of_term": true
}

Expected JSON schema for the output

The return value must match the following JSON schema.

JSON schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-04/schema#",
  "definitions": {},
  "properties": {
    "end_of_term": {
      "type": "boolean",
      "default": false,
      "examples": [
        true
      ]
    }
  },
  "additionalProperties": false
}

Was this article helpful?