Coverage pricing is a common requirement in industries offering service contracts or warranties alongside physical products. Coverage pricing is often calculated as a percentage of the price of the item it covers, which requires dynamic linkage between the covered item and its corresponding service or warranty product. This post walks you through implementing such coverage pricing using the CPQ Pricing Engine with the latest Advanced Price Model, all without the need for legacy Commerce scripts or complex multi-step workflows.

How Coverage Pricing Works

Coverage pricing ensures that the price of a service or warranty (the “coverage product”) is automatically derived as a percentage of the customer’s price for the item being covered (the “covered product”). For example, a service plan might cost 10% of the covered laptop’s sale price. This ensures consistent, transparent, and scalable pricing while improving the quoting experience.

To support this scenario in CPQ, Oracle recommends using:

  • Scripting Price Model Pricing Matrix Template
  • Advanced Price Model
  • Pricing Attributes mapping Commerce fields to Pricing Engine inputs
  • Pricing Rules to activate and apply the logic

Below are the sample configurations and code snippets you can adapt to your implementation.


Step-by-Step Configuration

1. Create Required Pricing Attributes

Begin by defining Pricing Attributes mapped to key transaction line attributes.

a) Coverage Flag Attribute

Create new transaction line Pricing Attribute for `_is_coverage` standard commerce process attribute. This attribute is required to set price model rule conditions. See Add a Pricing Attribute for more info.

Map the transaction line Pricing Attribute to the `_is_coverage` standard commerce process attribute. See Update Pricing Attribute Mappings for more info.

POST /rest/{{version}}/pricingSetup/attributes
    {
      "name": "Is Coverage Product",
      "variableName": "isCoverageProduct_c",
      "dataType": "Boolean",
      "defaultValue": true,
      "attributeLevel": "Line"
    }

 

PATCH /rest/{{version}}/pricingSetup/attributes/isCoverageProduct_c/mappings
    [
      {
        "op": "add",
        "value": {
          "dataSourceProviderVariableName": "oraclecpqo",
          "orderNumber": 1,
          "variableName": "_is_coverage"
        },
      "path": "/"
      }
    ]

b) Covered Product Line Number Attribute

Create new transaction line Pricing Attribute for `_s_coverageLineNumber` standard commerce process attribute. This attribute is used to determine which product list price is used to calculate the coverage fee. Map the transaction line Pricing Attribute to the `_s_coverageLineNumber` standard commerce process attribute.

POST /rest/{{version}}/pricingSetup/attributes
    {
      "name": "Covered Product Line Number",
      "variableName": "coveredProductLineNumber_c",
      "dataType": "String",
      "defaultValue": "",
      "attributeLevel": "Line"
    }

 

PATCH /rest/{{version}}/pricingSetup/attributes/coveredProductLineNumber_c/mappings
    [
      {
        "op": "add",
        "value": {
          "dataSourceProviderVariableName": "oraclecpqo",
          "orderNumber": 1,
          "variableName": "_s_coverageLineNumber"
       },
        "path": "/"
      }
    ]

2. Create the Pricing Matrix Template

Defines the input schema (attributes) and output fields for coverage calculation. See Add a Pricing Matrix Template for more info.

POST /rest/{{version}}/pricingSetup/matrixTemplates
    
    {
      "name": "Coverage Pricing",
      "variableName": "coveragePricingTemplate",
      "templateType": "scriptingPriceModel",
      "description": "Matrix for runtime coverage price calculation",
      "active": false
    }

Add Template Columns for Sales Channel and Coverage Fee Percent

Create pricing matrix template with two columns: sales channel and coverage fee percent. The sales channel column must not be set to ‘required’ in order to set data in the matrix that is null. See Create Pricing Matrix Template Columns for more info.

POST /rest/{{version}}/pricingSetup/matrixTemplates/coveragePricingTemplate/columns
    {
      "name": "Sales Channel",
      "variableName": "salesChannel",
      "description": "Sales channel",
      "dataType": "String",
      "defaultValue": "ZPM_DIRECT_CHANNEL_TYPES",
      "defaultOverridable": true,
      "negotiable": false,
      "key": true,
      "required": false
    }
    
    POST /rest/{{version}}/pricingSetup/matrixTemplates/coveragePricingTemplate/columns
    {
      "name": "Coverage Fee Percent",
      "variableName": "coverageFeePercent",
      "description": "Fee based on % of product price",
      "dataType": "Decimal",
      "defaultOverridable": false,
      "negotiable": false,
      "key": false,
      "required": false
     }

3. Add Coverage Pricing Matrix

The pricing matrix is based on a pricing matrix template and contains data for each of the columns defined in the template. These values are used to calculate prices. See Add a Pricing Matrix for more info.

POST /rest/{{version}}/pricingSetup/matrixes
    {
      "name": "Coverage Pricing Matrix",
      "variableName": "coveragePricingMatrix",
      "templateVariableName": "coveragePricingTemplate",
      "description": "Holds coverage fee percent values"
    }


4. Create and Configure the Advanced Price Model

Price models act as a container for prices and contain a set of price points, discounts, or markups that can be applied based simple conditions or conditions from linked pricing rules.
Setup price model with a simple ‘always true’ rule that will apply to all products. This is necessary because pricing for our coverage product depends on the associated hardgood product, 
so we need to make sure all the products in the quote are passed through to our advanced script.

See Add a Price Model for more info.

POST /rest/{{version}}/pricingSetup/models
    {
      "name": "Coverage Pricing Model",
      "variableName": "coveragePricingModel",
      "description": "Model applies coverage fee percentage charges.",
      "valueType": "advancedByTemplate",
      "dynamicPricingType": "advanced",
      "listType": "advancedByTemplate",
      "conditionType": "alwaysTrue",
      "matrixTemplateVariableName": "coveragePricingTemplate",
      "matrixTemplateName": "Coverage Pricing",
      "scriptingMatrixVariableName": "coveragePricingMatrix"
    }

 

5. Populate the Pricing Matrix

In this step we add data to the matrix for each of the columns referenced in the pricing matrix template – sales channel and the coverage fee percent. See Update Pricing Matrix Data for more info.

PATCH /rest/{{version}}/pricingSetup/matrixes/coveragePricingMatrix/data
    [
      {"op": "add", "path": "/", "value": {"salesChannel": "selfService", "coverageFeePercent": 10}},
      {"op": "add", "path": "/", "value": {"salesChannel": "eCommerce", "coverageFeePercent": 9}},
      {"op": "add", "path": "/", "value": {"salesChannel": null, "coverageFeePercent": 12}}
    ]

 

6. Activate by Linking to a Pricing Rule

Your price model needs to be deployed. To do so, you just need to link the price model into an active pricing rule. We’ll use the default pricing rule in this case. See Update Pricing Models Linked to Pricing Rule for more info.

PATCH /rest/{{version}}/pricingSetup/rules/_defaultPricingRule/ruleModels
    [
      {
        "op": "add",
        "value": {
          "variableName": "coveragePricingModel"
        },
        "path": "/"
      }
    ]
    
    
    

7. Scripting Price Model – Coverage Calculation Logic

Below is a BML script example for advanced coverage calculation. This script is referenced in your matrix template and embodies the “percentage of covered product” logic. See Example Advanced Price Model & Scripting Matrix Template for detailed examples.

/*
    Coverage Pricing Fee advanced script
    - Get sales channel from transaction header. 
    - Lookup coverage percentage from current price model matrix based on sales channel.
    - Find all transaction lines that are coverage products (isCoverageProduct_c)
    - For each coverage product, lookup the covered product by transaction line (coveredProductLineNumber_c)
    - Calculate coverage price based on covered product unit price * coverage percent
    - Update JSON response
    */
    
    // Get sales channel from transaction header
    salesChannel = jsonget(pricingDocument,"salesChannel_c","string", "ZPM_DIRECT_CHANNEL_TYPES");
    coverageFeePercent = 0.0;
    
    // Query the pricing data of this price model to get the coverage fee percentage
    resultSet = bmql("select coverageFeePercent from $_currentMatrix where salesChannel = $salesChannel or salesChannel is null");
    for result in resultSet {
      coverageFeePercent = getfloat(result, "coverageFeePercent");
    }
    
    // Get array of JSON paths for all coverage products (to return references, retrieve the path(s) and use jsonpathgetsingle)
    coverageProductPaths = jsonpathgetmultiple(pricingDocument,"$..[?(@.isCoverageProduct_c == true)]", true);
    
    indexes = jsonarraysize(coverageProductPaths);
    arr = string[indexes];
    i = 0;
    
    for e in arr {
      // Get JSON path to coverage product
      path = jsonarrayget(coverageProductPaths,i);
    
      coverageProduct = jsonpathgetsingle(pricingDocument, path, "json");
      coveredProductLineNumber = jsonget(coverageProduct,"coveredProductLineNumber_c","string");
    
      if (coveredProductLineNumber == "") {
        continue;
      }
    
      // Get the unitPrice for the covered product by line number
      coveredProductUnitPrice = jsonpathgetsingle(pricingDocument,"$..[?(@._itemIdentifier == '"+coveredProductLineNumber+"')].charges..['unitPrice']");
      if (isnumber(coveredProductUnitPrice)){
        // Calculate coverage product pricing as percent of covered product unit price
        coverageProductUnitPrice = atof(coveredProductUnitPrice)*(coverageFeePercent/100);
        // Get the reference of the charges array
        charges = jsonget(coverageProduct,"charges","jsonarray");
    
        charge = json();
        //Check if charges is empty
        if (jsonarraysize(charges) == 0){
          charge = jsonarrayappend(charges,json());
        } else {
          chargeIndexes = range(jsonarraysize(charges));
          for chargeIndex in chargeIndexes {
            // Get reference to first object in charges array
            charge = jsonarrayget(charges, chargeIndex, "json");
            break;
          }
       }
    
       // Update coverage product charge unit price
       jsonput(charge,"unitPrice", coverageProductUnitPrice);
       jsonput(charge, "isProductPrice", true);
       jsonput(charge, "priceType", "Recurring");
       jsonput(charge, "pricePeriod", "Per Year");
       jsonput(charge, "chargeType", "ORA_SALE");
       jsonput(charge, "dynamicPricingType", "static");
    
       calculationInfoArr = jsonput(charge,"calculationInfo",jsonarray());
       newCalculationInfo = jsonarrayappend(calculationInfoArr,json());
       jsonput(newCalculationInfo, "_priceProfileVar", "coveragePricingModel");
       jsonput(newCalculationInfo, "_priceProfileName", "Coverage Pricing Model");
       jsonput(newCalculationInfo, "_isAgreement", false);
       jsonput(newCalculationInfo, "_runningUnitPrice", coverageProductUnitPrice);
       jsonput(newCalculationInfo, "_pricingEngineRuleVar", "_defaultPricingRule");
       jsonput(newCalculationInfo, "_pricingEngineRuleName", "Base Pricing Rule");
    
       // Increment counter
       i=i+1;
      }
    }
    
    return pricingDocument;

 

Summary

By configuring your attributes, matrix templates, and price model as described, you can:

  • Dynamically calculate and manage coverage pricing as a percentage of covered product prices
  • Support coverage pricing for multiple sales channels and product types
  • Avoid unnecessary Commerce scripting or user extra steps
  • Maintain all pricing logic and data in the Pricing Engine for streamlined future maintenance

Need help?
Check out Oracle’s Customer Connect for more guidance. Happy hunting!

~sn