Tiered Pricing

Updated June 07, 2016

Tiered Pricing is a means of providing products or services at different price points. In this type of pricing model, the product rates are bundled into separate pricing tiers. Once a tier is filled up, the product will move to the next tier and a different price will be charged.

Chargebee

If you're looking to set up Volume Pricing, please refer to our guide here.

Use Case 1: Subscription created with 10 units

When a Subscription is created with a quantity of 10 units, all units will be charged $15, as the number of units would fall in the Tier 1 range.

Chargebee

Use Case 2: Subscriptions created with 50 units

When a Subscription is created with a quantity of 50 units, the price would be calculated across all four tiers.

Chargebee
Chargebee
Set up

Here's a quick guide on how to set up Tiered Pricing using API and JSON payload.

Step 1

Create a $0 Base Plan (e.g. Plan name Cloud Server) with Flat Fee pricing in your Chargebee site. The only purpose of this Plan would be to define the billing frequency of the product (monthly, quarterly, etc.).

cloud_server_base Cloud Server (Base) $0.00

Step 2

Create a Recurring Addon (quantity based) for every tier as shown below. Ensure that the Addon frequency matches the frequency of the Plan.

cloud_server_01_20 Cloud Server (01 to 20) $15.00
cloud_server_21_25 Cloud Server (21 to 25) $10.00
cloud_server_26_30 Cloud Server (26 to 30) $8.00
cloud_server_31 Cloud Server (31+) $5.00

Step 3

Add the JSON Meta data to the $0 Base Plan. Chargebee has the Meta data feature that can be used to store additional data at different levels (Plans, Subscriptions, Customers etc). Add JSON Meta data to the $0 Base Plan, to establish a relationship between "the unit range and the Addons.

                                
{"tiered_pricing": [
   {
       "addon_id": "cloud_server_01_20",
       "start_range": 1,
       "end_range": 20 
   },
   {
       "addon_id": "cloud_server_21_25",
       "start_range": 21,
       "end_range": 25
   },
   {
       "addon_id": "cloud_server_26_30",
       "start_range": 26,
       "end_range": 30
   },
   {
       "addon_id": "cloud_server_31",
       "start_range": 31
   }
]}
                                
                            
function tieredPricingUpdatePlanMetaData($planId) {
  $tieredPricing = array( "tiered_pricing" => array(
     array("addon_id" => $planId . "_01_20", 
           "start_range" => 1, 
           "end_range" => 20),
     array("addon_id" => $planId . "_21_25", 
           "start_range" => 21, 
           "end_range" => 25),
     array("addon_id" => $planId . "_26_30", 
           "start_range" => 26, "end_range" => 30),
     array("addon_id" => $planId . "_31", 
            "start_range" => 31)
     )
  );

  $planResult = ChargeBee_Plan::update($planId, 
           array("metaData" => json_encode($tieredPricing)));
}

def tiered_pricing_update_plan_meta_data(plan_id)
 tiered_pricing = {:tiered_pricing => [
    {  :addon_id => "#{plan_id}_01_20",
       :start_range => 1,
       :end_range => 20
    },
    {  :addon_id => "#{plan_id}_21_25",
       :start_range => 21,
       :end_range => 25
    },
    {  :addon_id => "#{plan_id}_26_30",
       :start_range => 26,
       :end_range => 30
    },
    {  :addon_id => "#{plan_id}_31",
       :start_range => 31
    }
  ]}
  result = ChargeBee::Plan.update(plan_id, {
    :meta_data => tiered_pricing.to_json
  })
end

def tiered_pricing_update_plan_meta_data(plan_id):
    tiered_pricing = {
       "tiered_pricing" : [
            { "addon_id" :  "cloud_server_01_20",
              "start_range" : 1,
              "end_range" : 20
            },
            { "addon_id" :  "cloud_server_21_25",
              "start_range" : 21,
              "end_range" : 25
            },
            { "addon_id" : "cloud_server_26_30",
              "start_range" : 26,
              "end_range" : 30
            },
            { "addon_id" : "cloud_server_31",
              "start_range" : 31
            }
    ]}
    plan_result = chargebee.Plan.update(plan_id, 
         {"meta_data": json.dumps(tiered_pricing)})


function tieredPricingUpdatePlanMetaData(planId) {
var tiered_pricing = { "tiered_pricing": [
    { "addon_id" : planId + "_01_20",
      "start_range": 1,
      "end_range": 20
    },
    { "addon_id" : planId + "_21_25",
      "start_range": 21,
      "end_range": 25
    },
    { "addon_id" : planId + "_26_30",
      "start_range": 26,
      "end_range": 30
    },
    { "addon_id" : planId + "_31",
      "start_range": 31,
    }]
};

chargebee.plan.update(planId, {  meta_data: tiered_pricing })
    .request(function(error, result) {
      if(error){
        console.log(error);
      }else{
        console.log(result);
      }
});

}

public void tieredPricingUpdatePlanMetaData(String planId) 
       throws JSONException, IOException {
    JSONObject metaData = new JSONObject();
    JSONArray arr = new JSONArray();
    arr.put(addonObj(planId + "_01_20", 1, 20));
    arr.put(addonObj(planId + "_21_25", 21, 25));
    arr.put(addonObj(planId + "_26_30", 26, 30));
    arr.put(addonObj(planId + "_31", 31, 0));
    metaData.put("tiered_pricing", arr);
    
    Result planResult = Plan.update(planId)
            .metaData(metaData).request();
}
    

public JSONObject addonObj(String addonId, int startRange, 
        int endRange) throws JSONException {
    return new JSONObject().put("addon_id", addonId)
            .put("start_range", startRange)
            .put("end_range", endRange);
}

public void tieredPricingUpdatePlanMetaData(String planId){
    JObject metaData = new JObject();
    JArray arr = new JArray();
    arr.Add(addonObj(planId + "_01_20", 1, 20));
    arr.Add(addonObj(planId + "_21_25", 21, 25));
    arr.Add(addonObj(planId + "_26_30", 26, 30));
    arr.Add(addonObj(planId + "_31", 31, 0));
    metaData.Add(new JProperty("tiered_pricing", arr));

    EntityResult planResult = Plan.Update(planId)
        .MetaData(metaData).Request();
}


public JObject addonObj(String addonId, int startRange, 
            int endRange) {
    return new JObject (new JProperty("addon_id",addonId),
        new JProperty("start_range",startRange),
        new JProperty("end_range",endRange));
}

Step 4

Create a Subscription with the Tiered Plan and Addons

Based on the Plan's metadata, pick the appropriate Addon(s) and create the Subscription with the required range. The Plan's metadata can be queried before Subscription creation to receive the Addon and Tier details.

How to change quantity for Subscriptions with Tiered Pricing

When a Subscription's quantity has to be changed, you can create the new set of Addons using the Update Subscription API.

Let's consider the following scenario:

A Subscription is created with a quantity of 25 units. The price for this, hence, will be calculated using 2 tiers.

1-20 Units Tier 1
21-25 Units Tier 2

The Customer now wants the Subscription's quantity to be changed from
25 to 5 units.

In this case, call the Update a Subscription API with replace_addon_list parameter set to true and pass the Addon quantities as per the change in Subscription.

Chargebee
function tieredPricingUpdateSubscription($planId,
       $subscribeQty, $subscriptionId) {

  $planResult = ChargeBee_Plan::retrieve($planId);
  $metaData = $planResult->plan()->metaData;
  $tieredPricing = $metaData["tiered_pricing"];
  $addons = array();
  for($i=0; $i < count($tieredPricing); $i++) {
    $addonObj = $tieredPricing[$i];
    $addonId = $addonObj["addon_id"];
    $startRange = $addonObj["start_range"];
    $endRange = isset($addonObj["end_range"]) ?
                       $addonObj["end_range"] : 0;

    if($subscribeQty < $startRange) {
     break;
    }

    $addonQty = 0;
    if($subscribeQty <= $endRange || $endRange == 0 ) {
         $addonQty = $subscribeQty - $startRange;
    } else {
         $addonQty =  $endRange - $startRange;
    }
    $addonQty = $addonQty + 1;
    $addons[] = array("id" => $addonId,
                      "quantity"=> $addonQty);
  }

  $result = ChargeBee_Subscription::update($subscriptionId,
          array("replaceAddonList"=> "true",
                "addons" => $addons));
}
def tiered_pricing_update_subscription(plan_id, 
          subscription_id, subscribe_qty)
  plan_result = ChargeBee::Plan.retrieve(plan_id)
  meta_data = plan_result.plan.meta_data
  tiered_pricing = meta_data[:tiered_pricing]
  addons = Array.new
  tiered_pricing.each do |addon_obj|
    addon_id = addon_obj[:addon_id]
    start_range = addon_obj[:start_range]
    end_range = addon_obj[:end_range]
    if subscribe_qty < start_range
      break
    end
    addon_qty = 0
    if end_range == nil || subscribe_qty <= end_range
       addon_qty = subscribe_qty - start_range
    elsif
       addon_qty = end_range - start_range
    end
    addon_qty = addon_qty + 1
    addons.push({:id => addon_id, :quantity => addon_qty })
 end
 result = ChargeBee::Subscription.update(subscription_id,
              {:replace_addon_list => "true", 
               :addons => addons})                        

end
def tiered_pricing_update_subscription(plan_id, 
      subscription_id, subscribe_qty):
   plan_result = chargebee.Plan.retrieve(plan_id)
   meta_data = plan_result.plan.meta_data
   tiered_pricing = meta_data["tiered_pricing"]
   addons = []
   for addon_obj in tiered_pricing:
       addon_id = addon_obj.get("addon_id")
       start_range = addon_obj.get("start_range")
       end_range = addon_obj.get("end_range", None)
       addon_qty = 0
       if subscribe_qty < start_range:
          break
       if end_range == None or subscribe_qty <= end_range:
           addon_qty = subscribe_qty - start_range
       else:
           addon_qty = end_range - start_range

       addon_qty = addon_qty + 1
       addons.append({"id": addon_id, "quantity" : addon_qty})
   result = chargebee.Subscription.update(subscription_id,
             {"replace_addon_list" : "true", 
              "addons" : addons})    

function tieredPricingUpdateSubscription(planId,
      subscribeQty, subscriptionId ) {

 chargebee.plan.retrieve(planId)
     .request(function(planError, planResult) {
   if(planError) {
      console.log(planError);
   } else {
      var metaData = planResult.plan.meta_data;
      var tieredPricing = metaData.tiered_pricing;
      var addons = [];
      for(var i=0; i < tieredPricing.length; i++) {
        var addonObj = tieredPricing[i];
        var addonId = addonObj.addon_id
        var startRange = addonObj.start_range
        var endRange = addonObj.end_range
        var addonQty = 0;
        if(subscribeQty < startRange) {
           break;
        }
        if( typeof(endRange) === 'undefined' || 
                         subscribeQty <= endRange) {
           addonQty = subscribeQty - startRange;
        } else {
           addonQty = endRange - startRange;
        }
        addonQty = addonQty + 1;
        addons.push({ "id" : addonId, "quantity" : addonQty})
      }
      chargebee.subscription.update(subscriptionId,
        { "replace_addon_list" : "true",
          "addons" : addons })
         .request(function(error, result){
              if(error) {
                console.log(error);
              } else {
                console.log(result);
              }
      })
   }
 })
}

public void tieredPricingUpdateSubscription(String planId,
        int subscribeQty, String subscriptionId) 
        throws Exception {
    Result planResult = Plan.retrieve(planId).request();
    JSONArray tieredPricing = planResult.plan().metaData()
            .getJSONArray("tiered_pricing");
    Subscription.UpdateRequest updateRequest = Subscription
            .update(subscriptionId);
    int addonIndex = 0; 
    for(int i=0;i < tieredPricing.length(); i++) {
        JSONObject obj = (JSONObject) tieredPricing.get(i);
        String addonId = obj.getString("addon_id");
        int startRange =  obj.getInt("start_range");
        int endRange = obj.getInt("end_range");
        if(subscribeQty < startRange) { 
            break;
        }
        int addonQty = 0;
        if(subscribeQty <= endRange || endRange == 0) {
            addonQty = subscribeQty - startRange;
        } else {
            addonQty = endRange - startRange; 
        }
        addonQty = addonQty + 1; 
        updateRequest.addonId(addonIndex, addonId)
                .addonQuantity(addonIndex, addonQty);
        addonIndex++;
    }
    Result result = updateRequest.replaceAddonList(true)
            .request();
}

public void tieredPricingUpdateSubscription(String planId, 
    int subscribeQty,String subscriptionId) {
    EntityResult planResult = Plan.Retrieve(planId)
                                .Request();
    JArray tieredPricing = planResult.Plan
        .MetaData ["tiered_pricing"].Value<JArray>();
    Subscription.UpdateRequest updateRequest = Subscription
        .Update(subscriptionId);
    int addonIndex = 0; 
    for(int i=0;i < tieredPricing.Count; i++) {
        JObject obj = (JObject)tieredPricing [i];
        String addonId = (string)obj ["addon_id"];
        int startRange =  (int)obj ["start_range"];
        int endRange = (int)obj ["end_range"];
        if(subscribeQty < startRange) { 
            break;
        }
        int addonQty = 0;
        if(subscribeQty <= endRange || endRange == 0) {
            addonQty = subscribeQty - startRange;
        } else {
            addonQty = endRange - startRange; 
        }
        addonQty = addonQty + 1; 
        updateRequest.AddonId(addonIndex, addonId)
            .AddonQuantity(addonIndex, addonQty);
        addonIndex++;
    }
    EntityResult result = updateRequest
        .ReplaceAddonList(true).Request();
}

Note: Ensure that other Addons belonging to the Subscription are not removed in this process.