Volume Pricing

Updated June 07, 2016

Volume Pricing is a pricing model used to define the price of the product with respect to the quantity purchased. Typically, the product price decreases with increasing quantity.

For instance, a license provider can set the price based on the number of licenses purchased. You might be paying $10 per license until you reach 5 but once you cross that figure, the cost might decrease to $8 per license, as shown below.

Chargebee

Unlike Tiered Pricing, where the product price of each unit may differ based on the unit range, in the case of Volume Pricing, all units of the product will cost the same.

Use Case 1: Subscription created with 5 units

When a Subscription is created with a quantity of 5 units, all units will be charged $10 each, as they will fall into the 'Volume 1' category.

Chargebee

Use Case 2: Subscriptions created with 20 units

When a Subscription is created with a quantity of 20 units, all units will be charged $6 each as they will fall into the 'Volume 4' category.

Chargebee
Chargebee
Set up

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

Step 1

Create Plans (e.g. Plan name Cloud Server) for each volume range and set the Plan price as shown below. The Plan created in Chargebee need not be a quantity based Plan.

cloud_server_01_05 Cloud Server (01 to 05) $10.00
cloud_server_06_10 Cloud Server (06 to 10) $8.00
cloud_server__11_15 Cloud Server (11 to 15) $6.50
cloud_server_16+ Cloud Server (16+) $6.00

Step 2

Add JSON Meta data to the Volume Based Plan. Chargebee has the Meta data feature that can be used to store additional data at different levels (Plans, Subscriptions, Customers etc). Add the JSON Meta data to the Plans in order to establish quantity limits for each Plan.

                                
{"volume_pricing": [
    {
        "plan_id": "cloud_server_01_05",
        "min_qty": 1,
        "max_qty": 5
    },
    {
        "plan_id": "cloud_server_06_10",
        "min_qty": 6,
        "max_qty": 10
    },
    {
        "plan_id": "cloud_server_11_15",
        "min_qty": 11,
        "max_qty": 15
    },
    {
        "plan_id": "cloud_server_16",
        "min_qty": 16
    }
]}
                                
                            
function volumePricingPlanMetaData($planId) {
  $volumePricing = array("volume_pricing" => array(
     array("plan_id" => $planId . "_01_05",
           "min_qty" => 1,
           "max_qty" => 5),
     array("plan_id" => $planId . "_06_10",
           "min_qty" => 6,
           "max_qty" => 10),
     array("plan_id" => $planId . "_11_15",
           "min_qty" => 11,
           "max_qty" => 15),
     array("plan_id" => $planId . "_16",
            "min_qty" => 16)
  ));

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

def volume_pricing_update_plan_meta(plan_id)
  volume_pricing = { :volume_pricing => [
    { :plan_id => "#{plan_id}_01_05",
      :min_qty => 1,
      :max_qty => 5
    },
    {  :plan_id => "#{plan_id}_06_10",
       :min_qty => 6,
       :max_qty => 10,
    },
    { :plan_id => "#{plan_id}_11_15",
      :min_qty => 11,
      :max_qty => 15
    },
    { :plan_id => "#{plan_id}_16",
      :min_qty => 16
    }
  ]}
  result = ChargeBee::Plan.update(plan_id, {
    :meta_data => volume_pricing.to_json
  })
end

def volume_pricing_update_plan_meta_data(plan_id):
    volume_pricing = { "volume_pricing" : [
           { "plan_id" : plan_id + "_01_05",
             "min_qty" : 1,
             "max_qty" : 5
           },
           { "plan_id" : plan_id + "_06_10",
             "min_qty" : 6,
             "max_qty" : 10
           },
           { "plan_id" : plan_id + "_11_15",
             "min_qty" : 11,
             "max_qty" : 15
           },
           { "plan_id" : plan_id + "_16",
             "min_qty" : 16
           }
    ]}
    plan_result = chargebee.Plan.update(plan_id,
            {"meta_data": json.dumps(volume_pricing)})

function volumePricingUpdatePlanMetaData(planId) {
 var volumePricing = { "volume_pricing" : [
       {  "plan_id" : planId + "_01_05",
          "min_qty" : 1,
          "max_qty" : 5
       },
       {
          "plan_id" : planId + "_06_10",
          "min_qty" : 6,
          "max_qty" : 10
       },
       {
          "plan_id" : planId + "_11_15",
          "min_qty" : 11,
          "max_qty" : 15
       },
       {  "plan_id" : planId + "_16",
          "min_qty" : 16
       }
   ]}
  chargebee.plan.update(planId, { meta_data : volumePricing})
        .request(function(planError, planResult){
      if(planError) {
         console.log(planError);
      } else {
         console.log(planResult);
      }
  });
}

public void volumePricingUpdatePlanMetaData(String planId) 
        throws JSONException, IOException {
    JSONObject metaData = new JSONObject();
    JSONArray arr = new JSONArray();
    arr.put(volumePricingPlanObj(planId + "_01_05", 1, 5));
    arr.put(volumePricingPlanObj(planId + "_06_10", 6, 10));
    arr.put(volumePricingPlanObj(planId + "_11_15", 11, 15));
    arr.put(volumePricingPlanObj(planId + "_16", 16, 0));
    metaData.put("volume_pricing", arr);
    
    Result result = Plan.update(planId)
            .metaData(metaData).request();
}

public JSONObject volumePricingPlanObj(String planId, 
        int minQty, int maxQty) throws JSONException {
    return new JSONObject().put("plan_id", planId)
            .put("min_qty", minQty)
            .put("max_qty", maxQty);
}

public void volumePricingUpdatePlanMetaData(String planId){
    JObject metaData = new JObject();
    JArray arr = new JArray();
    arr.Add(volumePricingAddonObj(planId + "_01_05", 1, 5));
    arr.Add(volumePricingAddonObj(planId + "_06_10", 6, 10));
    arr.Add(volumePricingAddonObj(planId + "_11_15", 11, 15));
    arr.Add(volumePricingAddonObj(planId + "_16", 16, 0));
    metaData.Add(new JProperty("volume_pricing", arr));

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

public JObject volumePricingAddonObj(String planId, 
    int minQty, int maxQty){
    return new JObject (new JProperty("plan_id", planId),
        new JProperty("min_qty", minQty),
        new JProperty("max_qty", maxQty));
}

Step 3

Create Subscriptions with a Volume Based Plan. Based on the volume purchased by the Customer, check the Plan's Meta data and Create the Subscription with the appropriate Plan. The Plan's Meta data can be queried before Subscription creation to receive the Plan details.

How to change quantity for Subscriptions with Volume Pricing

When a Subscription's quantity has to be changed, you can change the Plan using the Update Subscription API.

Let's consider the following scenario:

A Subscription is created with a quantity of 10 units. Since the quantity is 10, the Plan for this Subscription will be 'Cloud Server (01 to 10)'. Now the Customer wants to change the quantity from 10 to 20 Units.

In this case, the Plan has to be changed from 'Cloud Server (01 to 10)' to 'Cloud Server (16+)', using the Update Subscription API call.

Chargebee
function volumePricingUpdateSubscription($subscriptionId,
        $hiddenPlanId, $subscribeQty) {
  // Retrieve meta data from hidden plan
  $planResult = ChargeBee_Plan::retrieve($hiddenPlanId);
  $metaData = $planResult->plan()->metaData;
  $volumePricing = $metaData["volume_pricing"];
  $updateSubParams = array();
  for($i=0; $i < count($volumePricing); $i++) {
    $planObj = $volumePricing[$i];
    $planId = $planObj["plan_id"];
    $minQty = $planObj["min_qty"];
    $maxQty = isset($planObj["max_qty"]) ?
                      $planObj["max_qty"] : 0;
    if( $minQty <= $subscribeQty &&
            ($subscribeQty <= $maxQty || $maxQty == 0)) {
      $updateSubParams["planId"] = $planId;
      $updateSubParams["planQuantity"] = $subscribeQty;
      break;
    }
  }
  $result = ChargeBee_Subscription::update($subscriptionId,
                               $updateSubParams);

}

def volume_pricing_update_subscription(hidden_plan_id,
       subscription_id, subscribe_qty)
  plan_result = ChargeBee::Plan.retrieve(hidden_plan_id)
  meta_data = plan_result.plan.meta_data
  volume_pricing = meta_data[:volume_pricing]
  update_sub_params = Hash.new
  volume_pricing.each do |plan_obj|
     plan_id = plan_obj[:plan_id]
     min_qty = plan_obj[:min_qty]
     max_qty = plan_obj[:max_qty]
     if min_qty <= subscribe_qty &&
           (max_qty == nil || subscribe_qty <= max_qty)
        update_sub_params[:plan_id] = plan_id
        update_sub_params[:plan_quantity] = subscribe_qty
        break
     end
  end
  result = ChargeBee::Subscription.update(subscription_id,
                            update_sub_params)
end

def volume_pricing_update_subscription(subscription_id,
         hidden_plan_id, subscribe_qty):
   plan_result = chargebee.Plan.retrieve(hidden_plan_id)
   meta_data = plan_result.plan.meta_data
   volume_pricing = meta_data["volume_pricing"]
   update_sub_params = {}
   for plan_obj in volume_pricing:
       plan_id = plan_obj.get("plan_id")
       min_qty = plan_obj.get("min_qty")
       max_qty = plan_obj.get("max_qty", None)
       if (min_qty <= subscribe_qty and
             (max_qty == None or subscribe_qty <= max_qty)):
          update_sub_params["plan_id"] = plan_id
          update_sub_params["plan_quantity"] = subscribe_qty
          break
   result = chargebee.Subscription.update(subscription_id,
                                  update_sub_params)

void volumePricingUpdateSubscription(String subscriptionId,
    String hiddenPlanId, int subscribeQty) throws Exception {

    Result planResult = Plan.retrieve(hiddenPlanId).request();
    JSONArray volumePricing = planResult.plan().metaData()
            .getJSONArray("volume_pricing");
    Subscription.UpdateRequest updateRequest = Subscription
            .update(subscriptionId);
    for(int i=0; i < volumePricing.length(); i++){
        JSONObject obj = volumePricing.getJSONObject(i);
        String planId = obj.getString("plan_id");
        int minQty = obj.getInt("min_qty");
        int maxQty = obj.getInt("max_qty");
        
        if(minQty <= subscribeQty && 
                (subscribeQty <= maxQty || maxQty == 0) ) {
            updateRequest.planId(planId)
                .planQuantity(subscribeQty);
            break;
        } 
    }
    
    Result result = updateRequest.request();         
}

void volumePricingUpdateSubscription(String subscriptionId, 
            String hiddenPlanId, int subscribeQty) {
    EntityResult planResult = Plan.Retrieve(hiddenPlanId)
                                .Request();
    JArray volumePricing = planResult.Plan
        .MetaData ["volume_pricing"].Value<JArray>();
    Subscription.UpdateRequest updateRequest = Subscription
        .Update(subscriptionId);
    for(int i=0;i < volumePricing.Count; i++) {
        JObject obj = (JObject)volumePricing [i];
        String planId = (string)obj ["plan_id"];
        int minQty =  (int)obj ["min_qty"];
        int maxQty = (int)obj ["max_qty"];

        if(minQty <= subscribeQty && 
            (subscribeQty <= maxQty || maxQty == 0) ) {
            updateRequest.PlanId(planId)
                .PlanQuantity(subscribeQty);
            break;
        } 
    }

    EntityResult result = updateRequest.Request();
}

function volumePricingUpdateSubscription(subscriptionId,
        hiddenPlanId, subscribeQty) {
  chargebee.plan.retrieve(hiddenPlanId)
        .request(function(planError, planResult){
    if(planError) {
       console.log(planError);
    } else {
       var metaData = planResult.plan.meta_data;
       var volumePricing = metaData.volume_pricing;
       var updateSubParams = {};
       for(var i=0; i< volumePricing.length; i++){
          var planObj = volumePricing[i];
          var planId = planObj.plan_id;
          var minQty = planObj.min_qty;
          var maxQty = planObj.max_qty;
          if(minQty <= subscribeQty &&
                (typeof(maxQty) === 'undefined' || 
                   subscribeQty <= maxQty))  {
            updateSubParams["plan_id"] = planId;
            updateSubParams["plan_quantity"] = subscribeQty;
            break;
          }
       }
       chargebee.subscription
            .update(subscriptionId, updateSubParams)
            .request(function(error, result) {
          if(error) {
            console.log(error);
          }else {
            console.log(result);
          }
       })

    }
  })
}