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
