CPQ Developer Guide
CPQ Developer Guide
Guide
Version 48.0, Spring ’20
@salesforcedocs
Last updated: April 9, 2020
© Copyright 2000–2020 salesforce.com, inc. All rights reserved. Salesforce is a registered trademark of salesforce.com, inc.,
as are other names and marks. Other marks appearing herein may be trademarks of their respective owners.
CONTENTS
INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
CHAPTER 1 Salesforce CPQ Developer Guide
In this chapter ... Start working with Salesforce CPQ API and plugins.
1
Salesforce CPQ Developer Guide Get Started with Salesforce CPQ API
2
Salesforce CPQ Developer Guide CPQ API Models
3
Salesforce CPQ Developer Guide CPQ API Models
4
Salesforce CPQ Developer Guide CPQ API Models
key Integer Each quote line and group has a key that is
unique amongst all other keys in the same
quote.
5
Salesforce CPQ Developer Guide CPQ API Models
controllingGroups Map<String,Set<Id>> The option IDs that this option depends on.
6
Salesforce CPQ Developer Guide CPQ API Models
7
Salesforce CPQ Developer Guide CPQ API Models
optionId Id The
SBQQ__ProductOption__c.Id.
8
Salesforce CPQ Developer Guide CPQ API Models
9
Salesforce CPQ Developer Guide CPQ API Models
10
Salesforce CPQ Developer Guide CPQ API Models
11
Salesforce CPQ Developer Guide CPQ API Models
12
Salesforce CPQ Developer Guide CPQ API Models
13
Salesforce CPQ Developer Guide CPQ Quote API
14
Salesforce CPQ Developer Guide CPQ Quote API
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter" -H
"Content-Type: application/json" -H "Authorization: Bearer token" -X POST -d
@quoteModel.json
This request body quoteModel.json file saves a quote. The context value is a JSON formatted string.
{"saver": "SBQQ.QuoteAPI.QuoteSaver", "model": "{\"record\":
{\"attributes\":{\"type\":\"SBQQ__Quote__c\",\"url\":\"/services/data/v41.0/sobjects/
SBQQ__Quote__c/a0l61000003kUlVAAU\"},
15
Salesforce CPQ Developer Guide CPQ Quote API
\"Name\":\"Q-00681\",\"Id\":\"a0l61000003kUlVAAU\"},\"nextKey\":2,\"netTotal\":0.00,\
"lineItems\":[],\"lineItemGroups\":[],\"customerTotal\":0.00}"}
An example response body after saving a quote. The actual response is a JSON formatted string.
{
"record": {
"attributes":{
"type":"SBQQ__Quote__c",
"url":"/services/data/v41.0/sobjects/SBQQ__Quote__c/a0l61000003kUlVAAU"
},
"Name":"Q-00681",
"Id":"a0l61000003kUlVAAU"
},
"nextKey":2,
"netTotal":0.00,
"lineItems":[],
"lineItemGroups":[],
"customerTotal":0.00
}
APEX Examples
Before saving the QuoteSaver example class, make sure that the CPQ Model classes are added as individual Apex classes in your org.
public with sharing class QuoteSaver {
public QuoteModel save(QuoteModel quote) {
String quoteJSON = SBQQ.ServiceRouter.save('SBQQ.QuoteAPI.QuoteSaver',
JSON.serialize(quote));
return (QuoteModel) JSON.deserialize(quoteJSON, QuoteModel.class);
}
}
For an example of the QuoteSaver class, run the following code in anonymous Apex.
QuoteModel quoteModel; //Use Read, Add Products, or Calculate APIs to obtain a QuoteModel
Note: The Calculate Quote API requires callback when evaluating quote-scoped product rules.
16
Salesforce CPQ Developer Guide CPQ Quote API
Formats
JSON, Apex
HTTP Method
PATCH
Authentication
Authorization: Bearer token
REQUEST
Parameter 1
Name: quote
Type: QuoteModel
Required: Yes
Description: Representation of SBQQ__Quote__c data. See CPQ API QuoteModel
Parameter 2
Name: callbackClass
Type: String
Required: Required for Apex Trigger
Description: This Apex class implements the CPQ CalculateCallback interface.
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteCalculator"
-H
"Content-Type: application/json" -H "Authorization: Bearer token" -X PATCH -d
"@quoteToCalculate.json"
This request body quoteToCalculate.json file calculates a quote. The context value is a JSON formatted string.
{"context":"{\"quote\":{\"record\":{\"attributes\":{\"type\":\"SBQQ__Quote__c\",
\"url\":\"/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p61000002NUmoAAG\"},
\"SBQQ__PartnerDiscount__c\":null,\"SBQQ__GenerateContractedPrice__c\":null,
\"SBQQ__QuoteProcessId__c\":\"a0m61000005NYxO\",
\"SBQQ__NetAmount__c\":100.0,\"SBQQ__CustomerDiscount__c\":null,
\"SBQQ__CustomerAmount__c\":100.0,\"SBQQ__PaymentTerms__c\":\"Net30\",
\"SBQQ__RenewalUpliftRate__c\":null,\"Name\":\"Q-00822\",\"SBQQ__Type__c\":\"Quote\",
\"SBQQ__SubscriptionTerm__c\":null,\"SBQQ__MarkupRate__c\":null,
\"SBQQ__OrderGroupID__c\":null,\"SBQQ__DistributorDiscount__c\":null,
\"SBQQ__OrderByQuoteLineGroup__c\":false,\"SBQQ__OrderBy__c\":null,
\"SBQQ__PricebookId__c\":\"01s61000000LI67AAG\",\"SBQQ__EndDate__c\":null,
\"SBQQ__Account__c\":null,\"SBQQ__StartDate__c\":null,
\"SBQQ__FirstSegmentTermEndDate__c\":null,\"SBQQ__BillingFrequency__c\":null,
\"SBQQ__LineItemsGrouped__c\":false,\"SBQQ__ExpirationDate__c\":null,
\"SBQQ__Primary__c\":false,\"SBQQ__LineItemCount__c\":1.0,\"SBQQ__MasterContract__c\":null,
\"SBQQ__EditLinesFieldSetName__c\":null,
\"Id\":\"a0p61000002NUmoAAG\",\"SBQQ__Unopened__c\":false,\"SBQQ__RenewalTerm__c\":null},
\"nextKey\":2,\"netTotal\":100.00,
\"netNonSegmentTotal\":100.0000,\"lineItems\":[{\"upliftable\":false,
\"targetCustomerTotal\":null,\"targetCustomerAmount\":null,
17
Salesforce CPQ Developer Guide CPQ Quote API
\"record\":{\"attributes\":{\"type\":\"SBQQ__QuoteLine__c\",
\"url\":\"/services/data/v41.0/sobjects/SBQQ__QuoteLine__c/a0l610000061yHpAAI\"},
\"SBQQ__CarryoverLine__c\":false,\"SBQQ__TermDiscountTier__c\":null,
\"SBQQ__VolumeDiscount__c\":null,\"SBQQ__BillingType__c\":null,
\"SBQQ__Discount__c\":null,\"SBQQ__ListPrice__c\":100.0,\"SBQQ__Existing__c\":false,
\"SBQQ__ProductName__c\":\"Product1\",\"SBQQ__SegmentIndex__c\":null,
\"SBQQ__DiscountTier__c\":null,\"SBQQ__SBCustomLevel__c\":null,\"SBQQ__ComponentTotal__c\":null,
\"SBQQ__RenewedSubscription__c\":null,
\"SBQQ__SubscriptionTargetPrice__c\":null,\"SBQQ__AllowAssetRefund__c\":false,
\"SBQQ__Optional__c\":false,\"SBQQ__PriorQuantity__c\":null,
\"SBQQ__Source__c\":null,\"SBQQ__Quote__c\":\"a0p61000002NUmoAAG\",\"SBQQ__SBCustomEndDate__c\":null,
\"SBQQ__ProratedListPrice__c\":100.0,\"SBQQ__ChargeType__c\":null,
\"SBQQ__UpliftAmount__c\":0.0,\"SBQQ__ComponentSubscriptionScope__c\":null,
\"SBQQ__OptionLevel__c\":null,\"SBQQ__NetPrice__c\":100.0,\"SBQQ__SBCustomCity__c\":null,
\"Id\":\"a0l610000061yHpAAI\",\"SBQQ__OriginalQuoteLineId__c\":null,
\"SBQQ__RegularPrice__c\":100.0,\"SBQQ__EffectiveQuantity__c\":1.0,\"SBQQ__Quantity__c\":1.0,
\"SBQQ__TaxCode__c\":null,\"SBQQ__sbCustom_Number1__c\":null,\"SBQQ__ContractedPrice__c\":null,
\"SBQQ__CostEditable__c\":false,\"SBQQ__Dimension__c\":null,
\"SBQQ__DynamicOptionId__c\":null,\"SBQQ__SBCustomSupport__c\":null,
\"SBQQ__PreviousSegmentUplift__c\":null,\"SBQQ__EndDate__c\":null,\"SBQQ__PreviousSegmentPrice__c\":null,
\"SBQQ__UpgradedSubscription__c\":null,\"SBQQ__SpecialPriceType__c\":null,
\"SBQQ__SegmentKey__c\":null,\"SBQQ__OptionDiscount__c\":null,\"SBQQ__RequiredBy__c\":null,
\"SBQQ__UpgradedAsset__c\":null,\"SBQQ__Bundled__c\":false,\"SBQQ__SBCustomColor__c\":null,
\"SBQQ__OptionType__c\":null,\"SBQQ__Number__c\":1.0,\"SBQQ__SubscriptionPercent__c\":null,
\"SBQQ__ComponentListTotal__c\":null,\"SBQQ__TermDiscountSchedule__c\":null,
\"SBQQ__Taxable__c\":false,\"SBQQ__Description__c\":null,\"SBQQ__ProductCode__c\":\"P1\",
\"SBQQ__OriginalPrice__c\":100.0,\"SBQQ__GenerateContractedPrice__c\":null,
\"SBQQ__NetTotal__c\":100.0,\"SBQQ__UnitCost__c\":null,\"SBQQ__SubscriptionCategory__c\":null,
\"SBQQ__Hidden__c\":false,\"SBQQ__NonDiscountable__c\":false,\"SBQQ__Markup__c\":0.0,\"SBQQ__RenewedAsset__c\":null,
\"SBQQ__GrossProfit__c\":null,\"SBQQ__MinimumPrice__c\":null,
\"SBQQ__BundledQuantity__c\":null,\"SBQQ__BatchQuantity__c\":null,
\"SBQQ__ProrateMultiplier__c\":1.0,\"SBQQ__CustomerPrice__c\":100.0,\"SBQQ__SubscriptionTerm__c\":null,
\"SBQQ__MarkupRate__c\":null,\"SBQQ__DistributorDiscount__c\":null,
\"SBQQ__DefaultSubscriptionTerm__c\":null,\"SBQQ__PriceEditable__c\":false,
\"SBQQ__ProratedPrice__c\":100.0,
\"SBQQ__ComponentUpliftedByPackage__c\":false,\"SBQQ__StartDate__c\":null,
\"SBQQ__NonPartnerDiscountable__c\":false,\"SBQQ__BlockPrice__c\":null,\"SBQQ__MaximumPrice__c\":null,
\"SBQQ__SubscriptionPricing__c\":null,\"SBQQ__AdditionalDiscount__c\":0.0,\"SBQQ__Favorite__c\":null,
\"SBQQ__ProductOption__c\":null,\"SBQQ__OptionDiscountAmount__c\":null,
\"SBQQ__TermDiscount__c\":null,\"SBQQ__MarkupAmount__c\":null,\"SBQQ__PartnerDiscount__c\":null,
\"SBQQ__ComponentDiscountedByPackage__c\":false,\"SBQQ__Renewal__c\":false,
\"SBQQ__CompoundDiscountRate__c\":null,\"SBQQ__UpgradedQuantity__c\":null,
\"SBQQ__AdditionalDiscountAmount__c\":null,\"SBQQ__Cost__c\":null,
\"SBQQ__PackageProductDescription__c\":null,\"SBQQ__PartnerPrice__c\":100.0,\"SBQQ__DiscountSchedule__c\":null,
\"SBQQ__ComponentCost__c\":null,\"SBQQ__SubscribedAssetIds__c\":null,
\"SBQQ__SubscriptionScope__c\":\"Quote\",\"SBQQ__Product__r\":{\"attributes\":
{\"type\":\"Product2\",\"url\":\"/services/data/v41.0/sobjects/Product2/01t610000033JNPAA2\"},
\"SBQQ__ExternallyConfigurable__c\":false,\"SBQQ__CostEditable__c\":false,\
"SBQQ__QuantityEditable__c\":true,\"SBQQ__Hidden__c\":false,\"SBQQ__ExcludeFromOpportunity__c\":false,
\"SBQQ__NonDiscountable__c\":false,\"Name\":\"Product1\",\"PricebookEntries\":
{\"totalSize\":1,\"done\":true,\"records\":[{\"attributes\":{\"type\":\"PricebookEntry\",
\"url\":\"/services/data/v41.0/sobjects/PricebookEntry/01u610000055KTeAAM\"},\
"UnitPrice\":100.0,\"Product2Id\":\"01t610000033JNPAA2\",\"IsActive\":true,
\"Pricebook2Id\":\"01s61000000LI67AAG\",\"Id\":\"01u610000055KTeAAM\"}]},
18
Salesforce CPQ Developer Guide CPQ Quote API
\"SBQQ__AssetConversion__c\":\"Oneperquoteline\",\"SBQQ__IncludeInMaintenance__c\":false,
\"SBQQ__PriceEditable__c\":false,\"SBQQ__ReconfigurationDisabled__c\":false,
\"SBQQ__DescriptionLocked__c\":false,\"SBQQ__NewQuoteGroup__c\":false,\"SBQQ__Optional__c\":false,
\"SBQQ__ExcludeFromMaintenance__c\":false,
\"SBQQ__AssetAmendmentBehavior__c\":\"Default\",\"ProductCode\":\"P1\",\"SBQQ__OptionSelectionMethod__c\":\"Click\",
\"SBQQ__SubscriptionType__c\":\"Renewable\",\"SBQQ__SubscriptionBase__c\":
\"List\",\"SBQQ__CustomConfigurationRequired__c\":false,\"SBQQ__NonPartnerDiscountable__c\":false,
\"SBQQ__DefaultQuantity__c\":1.0,\"SBQQ__PricingMethodEditable__c\":false,\"SBQQ__BlockPricingField__c\
":\"Quantity\",\"SBQQ__HasConfigurationAttributes__c\":false,\"SBQQ__Taxable__c\":false,
\"SBQQ__PricingMethod__c\":\"List\",\"Id\":\"01t610000033JNPAA2\"},\"SBQQ__SubscriptionBase__c\
":\"List\",\"SBQQ__BillingFrequency__c\":null,\"SBQQ__OriginalUnitCost__c\":null,
\"SBQQ__sbcustom_TwinField__c\":null,\"SBQQ__Bundle__c\":false,\"SBQQ__Product__c\":\
"01t610000033JNPAA2\",\"SBQQ__SpecialPrice__c\":100.0,\"SBQQ__PricingMethodEditable__c\":false,
\"SBQQ__Uplift__c\":0.0,\"SBQQ__PackageProductCode__c\":null,\"SBQQ__PricingMethod__c\":\"List\",
\"SBQQ__SegmentLabel__c\":null,\"SBQQ__AdditionalQuantity__c\":null,
\"SBQQ__DiscountScheduleType__c\":null},\"reconfigurationDisabled\":false,\"productQuantityScale\"
:null,\"productQuantityEditable\":true,\"productHasDimensions\":false,
\"parentItemKey\":null,\"parentGroupKey\":null,\"key\":2,\"dimensionType\":null,\"descriptionLocked\":false,
\"configurationType\":null,\"configurationEvent\":null,
\"amountDiscountProrated\":null}],\"lineItemGroups\":[],\"customerTotal\":100.00,\"channelDiscountsOffList\":false,
\"applyPartnerDiscountFirst\":false,\"applyAdditionalDiscountLast\":false}}"}
An example response body after successfully calculating a product. The actual response is a JSON formatted string.
{
"record": {
"attributes": {
"type": "SBQQ__Quote__c",
"url": "/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p610000040iumAAA"
},
"Id": "a0p61000002NUmoAAG",
"Name": "Q-00822"
},
"nextKey": 2,
"netTotal": 200,
"netNonSegmentTotal": 200,
"lineItems": [
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLine__c",
"url": "/services/data/v41.0/sobjects/SBQQ__QuoteLine__c/a0l61000003u09UAAQ"
},
"Id": "a0l610000061yHpAAI",
"SBQQ__NetTotal__c": 200
}
}
"lineItemGroups": []
}
19
Salesforce CPQ Developer Guide CPQ Quote API
Apex Examples
When you execute the Calculate API with an Apex trigger, you also need to create a quote calculator callback class. This class must
implement the CPQ CalculateCallback interface to save the quote after calculating it in the background.
global with sharing class MyCallback implements SBQQ.CalculateCallback {
global void callback(String quoteJSON){
SBQQ.ServiceRouter.save('SBQQ.QuoteAPI.QuoteSaver', quoteJSON);
}
}
Before saving the QuoteCalculator example class, make sure that the CPQ model classes are added as individual Apex classes in your
org.
public with sharing class QuoteCalculator {
For an example of the QuoteCalculator class, run the following code in anonymous Apex.
QuoteModel quoteModel; // Use Read or Add Products APIs to obtain a QuoteModel
quoteModel.lineItems[0].record.SBQQ__Quantity__c = 2;
QuoteCalculator calculator = new QuoteCalculator();
calculator.calculate(quoteModel, 'MyCallback');
20
Salesforce CPQ Developer Guide CPQ Quote API
Parameter 1
Name: uid
Type: String
Required: Yes
Description: The ID of the quote to read
RESPONSE
Type
QuoteModel
Description
The representation of SBQQ__Quote__c data.
REST Examples
curl "https://yourInstance.salesforce.com/services/apexrest/SBQQ/
ServiceRouter?reader=SBQQ.QuoteAPI.QuoteReader&uid=a0p610000040iumAAA"
-H "Content-Type: application/json" -H "Authorization: Bearer token" -X GET
An example response body after reading a quote. The actual response is a JSON formatted string.
{
"record": {
"attributes": {
"type": "SBQQ__Quote__c",
"url": "/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p610000040iumAAA"
},
"Id": "a0p610000040iumAAA",
"Name": "Q-00880"
},
"nextKey": 5,
"netTotal": 300,
"netNonSegmentTotal": 300,
"lineItems": [
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLine__c",
"url":
"/services/data/v41.0/sobjects/SBQQ__QuoteLine__c/a0l61000003u09UAAQ"
},
"Id": "a0l61000003u09UAAQ"
}
}
"lineItemGroups": [ ]
}
21
Salesforce CPQ Developer Guide CPQ Quote API
Apex Examples
Before saving the QuoteReader example class, make sure that the CPQ model classes are as individual Apex classes in your org.
public with sharing class QuoteReader {
For an example of the QuoteReader class, run the following code in anonymous Apex.
QuoteReader reader = new QuoteReader();
QuoteModel quote = reader.read('a0Wf100000J1vk1');
System.debug(quote);
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?loader=QuoteAPI.QuoteValidator"
-H
"Content-Type: application/json" -H "Authorization: Bearer token" -X PATCH -d
@quoteModel.json
22
Salesforce CPQ Developer Guide CPQ Quote API
The request body quoteModel.json file validates a quote. The context value is a JSON formatted string/serialization of a quote,
the same as the CPQ Save Quote API.
{"context": "{\"record\":
{\"attributes\":{\"type\":\"SBQQ__Quote__c\",\"url\":\"/services/data/v41.0/sobjects/SBQQ__Quote__c/a0l61000003kUlVAAU\"},
\"Name\":\"Q-00681\",\"Id\":\"a0l61000003kUlVAAU\"},\"nextKey\":2,\"netTotal\":0.00,\"lineItems\":[],\"lineItemGroups\":[],\"customerTotal\":0.00}"}
An example response body after creating the Apex job for generating the quote proposal.
// valid quote
[]
// invalid quote
[
"message 1",
"message 2",
"message 3"
]
Apex Examples
Before saving the Validator example class, make sure that the CPQ model classes are added as individual Apex classes in your org.
public with sharing class Validator {
Run the following code in anonymous Apex to get Apex job ID for generating and saving the quote proposal.
QuoteModel quoteModel; //Use Read Quote API to obtain a QuoteModel
23
Salesforce CPQ Developer Guide CPQ Quote API
Parameter 1
Name: quote
Type: QuoteModel
Required: Yes
Description: A representation of SBQQ__Quote__c data
Parameter 2
Name: products
Type: ProductModel[]
Required: Yes
Description: An array of representations of product data
Parameter 3
Name: groupKey
Type: Integer
Required: Required only for grouped quotes
Description: An index of the existing quote line group where you’re adding products (0 indexed by default)
Parameter 4
Name: ignoreCalculate
Type: Boolean
Required: Yes
Description: Always use true for this value
RESPONSE
Type
QuoteModel
Description
The representation of SBQQ__Quote__c data
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteProductAdder"
This request body adderContext.json file reads a product. The context value is a JSON formatted string.
{"context":"{\"quote\":{\"record\":{\"attributes\":{\"type\":\"SBQQ__Quote__c\",\"url\":\"/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p61000004IpR8AAK\"},
\"Name\":\"Q-00905\",\"Id\":\"a0p61000004IpR8AAK\"},\"nextKey\":2,\"netTotal\":0.00,\"lineItems\":[],\"lineItemGroups\":[],
\"customerTotal\":0.00},\"products\":[],\"groupKey\":0, \"ignoreCalculate\": true}"}
An example response body after adding a product. The actual response is a JSON formatted string.
{
"record": {
"attributes": {
24
Salesforce CPQ Developer Guide CPQ Quote API
"type": "SBQQ__Quote__c",
"url": "/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p61000004IpR8AAK"
}
},
"nextKey": 4,
"netTotal": 0.00,
"lineItems": [
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLine__c"
},
"SBQQ__Product__c": "01t610000033JNtAAM"
},
"productQuantityEditable": true,
"productHasDimensions": false,
"key": 3,
"descriptionLocked": false
},
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLine__c"
},
"SBQQ__Product__c": "01t610000033JNUAA2",
"SBQQ__Product__r": {
"attributes": {
"type": "Product2",
"url": "/services/data/v41.0/sobjects/Product2/01t610000033JNUAA2"
},
"Id": "01t610000033JNUAA2",
"Name": "Product 2",
"ProductCode": "P2"
}
}
}
],
"lineItemGroups": [
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLineGroup__c",
"url": "/services/data/v41.0/sobjects/SBQQ__QuoteLineGroup__c/a0k61000008WIF1AAO"
},
"SBQQ__Quote__c": "a0p61000004IpR8AAK",
"Id": "a0k61000008WIF1AAO",
"SBQQ__Number__c": 1.0,
"SBQQ__SeparateContract__c": false,
"Name": "Group1"
},
"key": 2,
"hasMultiSegmentLines": false
}
25
Salesforce CPQ Developer Guide CPQ Quote API
],
"customerTotal": 0.00
}
Apex Examples
Before saving the ProductAdder example class, make sure that the CPQ model classes are added as individual Apex classes in your org.
public with sharing class ProductAdder {
For an example of the ProductAdder, run the following code in anonymous Apex.
QuoteModel quoteModel; //Use Read Quote API to obtain a QuoteModelProductModel
productModel; //Use Read Product API to obtain a ProductModel
26
Salesforce CPQ Developer Guide CPQ Quote API
Authentication
Authorization: Bearer token
REQUEST
Parameter 1
Name: productId
Type: ID
Required: Yes
Description: The ID of the product record to load
Parameter 2
Name: pricebookId
Type: ID
Required: Yes
Description: The ID of the pricebook that contains the product record to load
Parameter 3
Name: currencyCode
Type: String
Required: Required only for multi-currency orgs
Description: The ISO code of a Salesforce currency where the product’s price is loaded
RESPONSE
Type
ProductModel
Description
The representation of product data
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.ProductAPI.ProductLoader&uid=01t610000033JNt"
-H "Content-Type: application/json" -H "Authorization: Bearer token" -X PATCH -d
"@loaderContext.json"
This request body loaderContext.json file reads a product. The context value is a JSON formatted string.
{"context" : "{\"pricebookId\": \"01sA0000000wuhg\", \"currencyCode\":\"USD\"}"}
An example response body after reading a product. The actual response is a JSON formatted string.
{
"record": {
"attributes": {
"type": "Product2",
"url": "/services/data/v42.0/sobjects/Product2/01tA0000005uzfZ"
},
"Id": "01tA0000005uzfZ",
"Name": "Apple"
}
27
Salesforce CPQ Developer Guide CPQ Quote API
"options": [],
"features": [],
"configuration": {}
}
Apex Examples
Before saving the ProductReader example class, make sure that the CPQ model classes are added as individual Apex classes in your org.
public with sharing class ProductReader {
For an example of the ProductReader class, run the following code in anonymous Apex.
ProductReader reader = new ProductReader();
ProductModel product = reader.read('01tj0000003P1SN','01sj0000003THhKAAW','USD');
System.debug(product);
28
Salesforce CPQ Developer Guide CPQ Quote API
Required: No
Description: The document name
Parameter 2
Name: paperSize
Type: String
Required: No
Description: Options: Default, Letter, Legal, A4
Parameter 3
Name: outputFormat
Type: String
Required: No
Description: Options: pdf, word. Defaults to pdf.
Parameter 4
Name: quoteID
Type: Id
Required: Yes
Description: The quote ID
Parameter 5
Name: templateId
Type: Id
Required: Yes
Description: The quote template ID
Parameter 6
Name: language
Type: String
Required: No
Description: Defaults to en_US
RESPONSE
Type
jobId
Description
Apex queueable job Id
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?saver=QuoteDocumentAPI.SaveProposal"
-H "Content-Type: application/json" -H
"Authorization: Bearer token" -X PATCH -d @data.json
29
Salesforce CPQ Developer Guide CPQ Quote API
The request body data.json file generates and saves a quote proposal.
"{\"saver\":\"SBQQ.QuoteDocumentAPI.Save\",\"model\":\"{\\\"name\\\":\\\"test\\\",\\\
"quoteId\\\":\\\"a0n0R000000jhVC\\\",\\\"templateId\\\":\\\"a0l0R000000vahe\\\",
\\\"outputFormat\\\":\\\"PDF\\\",\\\"language\\\":\\\"en_US\\\",\\\"paperSize\\\":\\\"Default\\\"}\"}"
An example response body after creating the Apex job for generating the quote proposal.
"7070R00000Nj8mjQAB"
Apex Examples
Before saving the GenerateQuoteProposal example class, make sure that the CPQ model classes are added as individual Apex classes in
your org.
public with sharing class GenerateQuoteProposal {
Run the following code in anonymous Apex to get Apex job ID for generating and saving the quote proposal.
QuoteProposalModel model = new QuoteProposalModel();
model.quoteId = 'a0n0R000000jhVC';
model.templateId = 'a0l0R000000vahe';
GenerateQuoteProposal proposalGenerator = new GenerateQuoteProposal();
String jobId = proposalGenerator.save(model);
System.debug(jobId);
30
Salesforce CPQ Developer Guide CPQ Quote API
Type: Id
Required: Optional
Description: The quote template record's ID
Parameter 3
Name: language
Type: String
Required: Optional
Description: Language code when using CPQ translations.
RESPONSE
Type
QuoteTermModel
Description
The representation of SBQQ__QuoteTerm__c data
REST Examples
curl "https://yourInstance.salesforce.com/services/apexrest/SBQQ/
ServiceRouter?loader=SBQQ.QuoteTermAPI.Load&uid=a0x5C000000G1CV"
-H "Content-Type: application/json" -H "Authorization: Bearer token" -X PATCH -d
"@termContext.json"
Example request body termContext.json file for reading quote terms. The context value is a JSON formatted string.
{"context":"{\"templateId\": \"a0v5C000000jTgr\", \"language\": \"es\"}"}
An example response body returning two quote terms. The actual response is a JSON formatted string.
[
{
"value": "Hasta 10 sesiones concurrentes incluidas.",
"type": "Standard",
"standardTermId": null,
"quoteId": null,
"locked": false,
"label": "1",
"id": "a0w5C000000cbaFQAQ"
},
{
"value": "$ 50USD / por mes por licencia de sesión adicional.",
"type": "Standard",
"standardTermId": null,
"quoteId": null,
"locked": false,
"label": "1.1",
"id": "a0w5C000000cbaKQAQ"
}
]
31
Salesforce CPQ Developer Guide CPQ Contract API
Apex Examples
Before saving the QuoteTermReader example class, make sure that the CPQ model classes are added as individual Apex classes in your
org.
public with sharing class QuoteTermReader {
For an example of the QuoteTermReader class, run the following code in anonymous Apex.
QuoteTermReader quoteTermReader = new QuoteTermReader();
List<QuoteTermModel> quoteTerms = quoteTermReader.load('a0x5C000000G1CV', 'a0v5C000000jTgr',
'es');
System.debug(quoteTerms);
32
Salesforce CPQ Developer Guide CPQ Contract API
Required: Yes
Description: 15-character case sensitive or 18-character case insensitive Salesforce Contract ID to amend
RESPONSE
Type
QuoteModel
Description
Representation of SBQQ__Quote__c data for an amendment quote
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.ContractManipulationAPI.ContractAmender&uid=800R00000000X4g"
An example response body after amending a quote. The actual response is a JSON formatted string.
{
"record": {
"attributes": {
"type": "SBQQ__Quote__c",
"url": "/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p610000040iumAAA"
},
"Id": "a0p610000040iumAAA",
"Name": "Q-00880"
},
"nextKey": 5,
"netTotal": 300,
"netNonSegmentTotal": 300,
"lineItems": [
{
"record": {
33
Salesforce CPQ Developer Guide CPQ Contract API
"attributes": {
"type": "SBQQ__QuoteLine__c",
"url":
"/services/data/v41.0/sobjects/SBQQ__QuoteLine__c/a0l61000003u09UAAQ"
},
"Id": "a0l61000003u09UAAQ"
}
}
"lineItemGroups": [ ]
}
An example response body after amending a quote. The actual response is a JSON formatted string.
{
"record": {
"attributes": {
"type": "SBQQ__Quote__c",
"url": "/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p61000004IpR8AAK"
}
},
"nextKey": 4,
"netTotal": 0.00,
"lineItems": [
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLine__c"
},
"SBQQ__Product__c": "01t610000033JNtAAM"
},
"productQuantityEditable": true,
"productHasDimensions": false,
"key": 3,
"descriptionLocked": false
},
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLine__c"
},
"SBQQ__Product__c": "01t610000033JNUAA2",
"SBQQ__Product__r": {
"attributes": {
"type": "Product2",
"url": "/services/data/v41.0/sobjects/Product2/01t610000033JNUAA2"
},
"Id": "01t610000033JNUAA2",
"Name": "Product 2",
"ProductCode": "P2"
}
}
}
],
"lineItemGroups": [
{
34
Salesforce CPQ Developer Guide CPQ Contract API
"record": {
"attributes": {
"type": "SBQQ__QuoteLineGroup__c",
"url": "/services/data/v41.0/sobjects/SBQQ__QuoteLineGroup__c/a0k61000008WIF1AAO"
},
"SBQQ__Quote__c": "a0p61000004IpR8AAK",
"Id": "a0k61000008WIF1AAO",
"SBQQ__Number__c": 1.0,
"SBQQ__SeparateContract__c": false,
"Name": "Group1"
},
"key": 2,
"hasMultiSegmentLines": false
}
],
"customerTotal": 0.00
}
Apex Examples
Before saving the ContractAmender example class, make sure that the CPQ model classes are added as individual Apex classes in your
org.
public with sharing class ContractAmender {
public QuoteModel load(String contractId) {
String quoteJSON =
SBQQ.ServiceRouter.load('SBQQ.ContractManipulationAPI.ContractAmender', contractId, null);
For an example of the ContractAmender, run the following code in anonymous Apex.
ContractAmender amender = new ContractAmender();
QuoteModel quote = amender.load('800R00000000X4g');
System.debug(quote);
35
Salesforce CPQ Developer Guide CPQ Contract API
Required: Yes
Description: JSON object
Attribute 1
Name: masterContractId
Type: Id
Required: No
Description: If you’re renewing multiple contracts, use the ID of the contract that you want to consider the master contract
Attribute 2
Type: ContractModel[]
Required: Yes
Description: One or more ContractModels to renew
RESPONSE
Type
QuoteModel[]
Description
Representation of one or more SBQQ__Quote__c data for renewal quotes
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?loader=SBQQ.ContractManipulationAPI.ContractRenewer"
-H
"Content-Type: application/json" -H "Authorization: Bearer token" -X POST -d
@context.json
36
Salesforce CPQ Developer Guide CPQ Contract API
This request body context.json file renews one or more Contracts. The context value is a JSON formatted string.
{"context": "{\"masterContractId\": null, \"renewedContracts\":
[{\"attributes\":{\"type\":\"Contract\"},\"Id\":\"800540000006LLVAA2\"}]}"}
An example response body after renewing a quote. The actual response is a JSON formatted string.
[{
"record": {
"attributes": {
"type": "SBQQ__Quote__c",
"url": "/services/data/v41.0/sobjects/SBQQ__Quote__c/a0p610000040iumAAA"
},
"Id": "a0p610000040iumAAA",
"Name": "Q-00880"
},
"nextKey": 5,
"netTotal": 300,
"netNonSegmentTotal": 300,
"lineItems": [
{
"record": {
"attributes": {
"type": "SBQQ__QuoteLine__c",
"url":
"/services/data/v41.0/sobjects/SBQQ__QuoteLine__c/a0l61000003u09UAAQ"
},
"Id": "a0l61000003u09UAAQ"
}
}
"lineItemGroups": [ ]
}]
Apex Examples
Before saving the ContractRenewer example class, make sure that the CPQ model classes are as individual Apex classes in your org.
// The following is used to generate a json context
private with sharing class RenewalContextTest {
public Id masterContractId;
public Contract[] renewedContracts;
}
// testInput is the Json
RenewalContextTest testInput = new RenewalContextTest();
testInput.masterContractId = '8001D000000AVIa'; // example Id
testInput.renewedContracts = [SELECT Id FROM Contract WHERE Id IN ('8001D000000AVIa',
'8001D000000AVIf')]; / example Ids
37
Salesforce CPQ Developer Guide Generate Quote Document API
}
}
For an example of the ContractAmender, run the following code in anonymous Apex.
ContractRenewer renewer = new ContractRenewer();
QuoteModel[] quotes = renewer.load(null, new String[]{'800R00000000X4g'});
System.debug(quotes);
Formats
JSON, Apex
HTTP Method
POST
Authentication
Authorization: Bearer token
Request
Table 1: Parameters
Name Type Required Definition
Name String No The document name
38
Salesforce CPQ Developer Guide Service Router
Response
Table 2: Parameters
Name Type Description
jobID ID Apex queueable job ID
REST Examples
curl
"https://yourInstance.salesforce.com/services/apexrest/SBQQ/ServiceRouter?saver=QuoteDocumentAPI.SaveProposal"
Example request body data.json file for generating and saving a quote document.
"{\"saver\":\"SBQQ.QuoteDocumentAPI.Save\",\"model\":\"{\\\"name\\\":\\\"test\\\",\\\"quoteId\\\"
:\\\"a0n0R000000jhVC\\\",\\\"templateId\\\":\\\"a0l0R000000vahe\\\",\\\"outputFormat\\\":
\\\"PDF\\\",\\\"language\\\":\\\"en_US\\\",\\\"paperSize\\\":\\\"Default\\\"}\"}"
Example response body after creating APEX job for creating the quote document.
"7070R00000Nj8mjQAB"
APEX Examples
Before saving the GenerateQuoteProposal example class, make sure that the CPQ model classes are added as individual APEX classes in
your org.
public with sharing class GenerateQuoteProposal {
Run the following code in anonymous Apex to get Apex job ID for generating and saving the quote proposal.
QuoteProposalModel model = new QuoteProposalModel();
model.quoteId = 'a0n0R000000jhVC';
model.templateId = 'a0l0R000000vahe';
GenerateQuoteProposal proposalGenerator = new GenerateQuoteProposal();
String jobId = proposalGenerator.save(model);
System.debug(jobId);
Service Router
SBQQ.ServiceRouter is a global Apex class serving as a single entry point for all API calls. You can
EDITIONS
use it for calls made by Apex code, Visualforce Remoting, or REST callouts.
SBQQ.ServiceRouter contains three global methods. Available in: Salesforce CPQ
Summer ’16 and later
39
Salesforce CPQ Developer Guide CPQ API Quickstart Guide
/**
* Note: this doesn’t perform a calculatation. Reference the calculate API to see how to
calculate a quote.
*/
//the Id of the pricebook for the quote and product being added
String pricebookId = '01sj0000003THhKAAW';
//the JSON formatted String representing the quote model to add a product to
String quoteModel = SBQQ.ServiceRouter.read('SBQQ.QuoteAPI.QuoteReader', quoteId);
//the JSON formatted String representing the product to be added to the quote
String productModel = SBQQ.ServiceRouter.load('SBQQ.ProductAPI.ProductLoader', productId,
'{"pricebookId" : "' + pricebookId + '", "currencyCode" : "' + currencyCode + '"}');
//the JSON formatted String representing the quote with the product added to it
String updatedQuoteModel = SBQQ.ServiceRouter.load('SBQQ.QuoteAPI.QuoteProductAdder', null,
40
Salesforce CPQ Developer Guide CPQ API Quickstart Guide
NODEJS
This example reads a quote, adds a product, calculates the quote, and saves it.
// 3rd party library to call into a Salesforce org
var jsforce = require('jsforce');
// log in to the org with with a valid username and password using jsforce
var conn = new jsforce.Connection({loginUrl: loginUrl});
conn.login(username, password).then(function () {
return Promise.resolve(conn);
})
.then(function (conn) {
// read both the quote and the product to add
var quotePromise =
conn.apex.get('/SBQQ/ServiceRouter?reader=SBQQ.QuoteAPI.QuoteReader&uid=' + quoteId);
var productPromise =
conn.apex.patch('/SBQQ/ServiceRouter?loader=SBQQ.ProductAPI.ProductLoader&uid=' + productId,
{
context: JSON.stringify({
pricebookId: pricebookId,
currencyCode: currencyCode
})
});
return Promise.all([quotePromise, productPromise]);
})
.then(function (models) {
// add the retrieved product to the retrieved quote in the first group
var quoteModel = JSON.parse(models[0]);
var productModel = JSON.parse(models[1]);
return conn.apex.patch('/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteProductAdder',
{
context: JSON.stringify({
41
Salesforce CPQ Developer Guide Disable CPQ Triggers in Apex
quote: quoteModel,
products: [productModel],
groupKey: 0,
ignoreCalculate: true
})
});
})
.then(function (quoteWithProduct) {
var quote = JSON.parse(quoteWithProduct);
// cacluate the quote with the added product
return conn.apex.patch('/SBQQ/ServiceRouter?loader=SBQQ.QuoteAPI.QuoteCalculator', {
context: JSON.stringify({
quote: quote
})
});
})
.then(function (calculatedQuote) {
var quote = JSON.parse(calculatedQuote);
// save the calucated quote
return conn.apex.post('/SBQQ/ServiceRouter', {
saver: 'SBQQ.QuoteAPI.QuoteSaver',
model: JSON.stringify(quote)
});
})
.then(function (savedQuoted) {
// log the quote has been saved
console.log('Quote finished processing', savedQuoted);
});
Note: TriggerControl disables only CPQ and Billing triggers. Other triggers or Salesforce logic, or your own triggers, validations,
workflow rules, or processes, are unaffected.
Methods
global static void disable();
Disables built-in CPQ triggers within the current transaction.
global static void enable();
Enables built-in CPQ triggers if they had previously been disabled.
global static Boolean isEnabled()
Returns true if CPQ triggers are currently enabled. Otherwise, returns false.
42
Salesforce CPQ Developer Guide Salesforce CPQ Plugins
Example:
SBQQ.TriggerControl.disable();
try {
// Do something simple and interesting without
// running triggers.
quote.MyStatus__c = ‘Red’;
update quote;
} finally {
SBQQ.TriggerControl.enable();
}
Example:
if (SBQQ.TriggerControl.isEnabled()) {
// Only run our logic if CPQ trigger logic is also enabled.
myRelatedObject.Quote__c = quote.Id;
insert myRelatedObject;
}
43
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
44
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
JSForce
JSForce is a third-party library that provides a unified way to perform queries, execute Apex REST calls, use theMetadata API, or make
HTTP requests remotely. Methods access jsforce through the optional parameter conn.
Note: The JSQCP is an ES6 module. It is transpiled via Babel and module-scoped by default. You can use any elements of the ES6
language or syntax. However, the plugin must be able to run in both browser and node environments. Global browser variables
such as window may not be available.
45
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
The calculator calls this method before formula fields are evaluated. Returns {promise}.
export function onInit(quoteLineModels) {
return Promise.resolve();
};
onBeforeCalculate
The calculator calls this method before calculation begins, but after formula fields have been evaluated. Returns {promise}.
export function onBeforeCalculate(quoteModel, quoteLineModels) {
return Promise.resolve();
};
onBeforePriceRules
The calculator calls this method before it evaluates price rules. Returns {promise}.
export function onBeforePriceRules(quoteModel, quoteLineModels) {
return Promise.resolve();
};
onAfterPriceRules
46
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
The calculator calls this method after it evaluates price rules. Returns {promise}.
export function onAfterPriceRules(quoteModel, quoteLineModels) {
return Promise.resolve();
};
onAfterCalculate
The calculator calls this method after it completes a calculation, but before re-evaluating formula fields. Returns {promise}
export function onAfterCalculate(quoteModel, quoteLineModels) {
return Promise.resolve();
};
isFieldVisible
Note: This method can’t be used to alter data.
The calculator calls this method after it completes a calculation. Returns {Boolean}
if (fieldName == 'SBQQ__Description__c') {
return false;
}
return true;
};
isFieldEditable
Note: This method can’t be used to alter data.
47
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
The calculator calls this method after it completes a calculation. Returns {Boolean}
if (fieldName == 'SBQQ__Description__c') {
return false;
}
return true;
};
48
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
}
line.record["True_Effective_End_Date__c"] = toApexDate(trueEndDate);
line.record["True_Effective_Term__c"] = trueTerm;
});
quote.record["True_Effective_End_Date__c"] = toApexDate(maxEffectiveEndDate);
quote.record["True_Effective_Term__c"] = maxEffectiveTerm;
}
return Promise.resolve()
}
ed.setUTCDate(ed.getUTCDate() - 1);
}
return ed;
}
/**
* Takes a JS Date object and turns it into a string of the type 'YYYY-MM-DD', which
is what Apex is expecting.
* @param {Date} date The date to be stringified
* @returns {string}
*/
function toApexDate(/*Date*/ date) {
if (date == null) {
return null;
}
// Get the ISO formatted date string.
// This will be formatted: YYYY-MM-DDTHH:mm:ss.sssZ
49
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
Note: The sample script assumes the Salesforce admin created a custom field Component
Custom Total on the Quote Line object.
Javascript
export function onInit(lines) {
if (lines != null) {
lines.forEach(function (line) {
line.record["Component_Custom_Total__c"] = 0;
});
}
};
50
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
0;
var cListPrice = line.ProratedListPrice__c || 0;
var cQuantity = line.Quantity__c == null ? 1 : line.Quantity__c;
var cPriorQuantity = line.PriorQuantity__c || 0;
var cPricingMethod = line.PricingMethod__c == null ? "List" :
line.PricingMethod__c;
var cDiscountScheduleType = line.DiscountScheduleType__c || '';
var cRenewal = line.Renewal__c || false;
var cExisting = line.Existing__c || false;
var cSubscriptionPricing = line.SubscriptionPricing__c || '';
parent.record["Component_Custom_Total__c"] = pComponentCustomTotal;
}
});
}
};
51
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
} else {
return qty;
}
}
Javascript
52
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
return Promise.resolve();
}
53
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
Insert Records
The sample JavaScript script can be used in the Quote Line Calculator to insert records.
EDITIONS
This sample JavaSciprt code exports all of the methods that the calculator looks for, and documents
their parameters and return types. Available in: Salesforce CPQ
Winter ’16 and later
54
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
}
return Promise.resolve();
}
Note:
• Salesforce CPQ prioritizes field-level security over page security plugins. If a field is read-only and an editability function for
that field returns True, the field remains read-only.
• Use the page security plugin only for hiding, showing, and adjusting the editability of fields. If you want change field values,
use the Javascript Quote Calculator Plugin.
• The quote line editor shows blank empty spaces for quote line drawer fields hidden by the page security plugin. To remove
these spaces, go to Salesforce CPQ line editor package settings and select Enable Compact Mode.
To create a page security plugin, define your code in a custom script record and then reference that record's name in the Quote Calculator
Plugin field within Salesforce CPQ Plugin package settings. If you’re already using a quote calculator plugin in that field, you can add
your page security plugin code to the calculator plugin’s custom script record.
55
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
quote or line SObject The object containing the field that you're
evaluating to determine whether
fieldName is visible.
quote or line SObject The object containing the field that you're
evaluating to determine whether
fieldName is editable.
We strongly recommend that users on Salesforce CPQ Summer '19 and later use the new functions given their improved flexibility. If
your plugin uses pre-Summer '19 functions with isFieldEditableForObject or isFieldVisibleForObject functions
using the line parameter, Salesforce CPQ ignores the new functions and uses the old functions instead.
56
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
To specify whether your changes apply to a field on the quote or on the quote line, use an if statement for your objectNamein
the isFieldVisibleForObject or isFieldEditableForObject code block. For example, in the following code
segment, we're targeting the Markup Rate field on the quote.
export function isFieldEditableForObject(fieldName, quote, conn, objectName){
if (objectName === 'Quote__c' && fieldName === 'SBQQ__MarkupRate__c')
However, the following code segment targets Markup Rate on both the quote and the quote line.
export function isFieldEditableForObject(fieldName, quote, conn, objectName){
if fieldName === 'SBQQ__MarkupRate__c'
Example: In this example, if a quote's Customer Discount is greater than 10%, we lock the quote's Markup Rate field from edits.
export function isFieldEditableForObject(fieldName, quote, conn, objectName){
if (objectName === 'Quote__c' && fieldName === 'SBQQ__MarkupRate__c') {
if (quote.SBQQ__CustomerDiscount__c > 10) {
return false;
}
}
}
Example: In this example, if a quote line's Distributor Discount is greater than 10%, we hide the quote line's Markup Rate field.
export function isFieldVisibleForObject(fieldName, line, conn, objectName){
if (objectName === 'QuoteLine__c' && fieldName === 'SBQQ__MarkupRate__c') {
if (line.SBQQ__CustomerDiscount__c > 10) {
return false;
}
}
}
Example: One function can also evaluate and act on quote and quote line fields at the same time, including twin fields. In this
example, if a quote's Customer Discount is greater than 10%, we lock the quote's Markup Rate field from edits. If the quote line's
Distributor Discount is greater than 10%, we hide the quote line's Markup Rate field.
Example:
export function isFieldEditableForObject(fieldName, quote, conn, objectName){
if (objectName === 'Quote__c' && fieldName === 'SBQQ__MarkupRate__c') {
if (quote.SBQQ__CustomerDiscount__c > 10) {
return false;
}
}
return false;
}
}
}
57
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin
Note: Salesforce CPQ has deprecated support for Apex page security plugins. Review Available in: All Salesforce
Javascript Page Security Plugin for information on the currently-supported version. CPQ Editions
The Legacy Page Security Plugin handles two types of use cases.
Show or hide fields on each quote line
For example, you’re selling training classes and you want to capture how many students are participating in the class. Use the page
security plugin to hide a student number field.
Make quote line fields read-only or writable
For example, you allow your users to specify the subscription term on each quote line, but you have some products that can only
be quoted on a 12-month basis. Use the page security plugin to make the Subscription Term field read-only for such products, while
keeping it writable for the other products.
To use the Legacy Page Security Plugin, first create an Apex class. Then enter the Apex class name in the Legacy Page Security Plugin
setting in the Salesforce CPQ package settings. You can call only one Apex class at a time in the Legacy Page Security Plugin.
Example:
global class MyPageSecurityPlugin implements SBQQ.PageSecurityPlugin2 {
public Boolean isFieldEditable(String pageName, Schema.SObjectField field) {
return null;
}
58
Salesforce CPQ Developer Guide Product Search Plugin
• Quote calculator plugins perform synchronous calculations in the quote line editor UI, within a standard web browser with all
expected platform and browser information available. However, asynchronous calculations occur within a Heroku application outside
of the web browser. If your plugin must reference the state of the platform running the calculation, make sure to account for whether
the quote line editor or Heroku is handling the calculation.
• If your plugin makes callouts to an endpoint that you own, make sure that both the local Salesforce host and your external Heroku
host can access the endpoint URI.
• The total time for a calculation plus the time for a callout to Heroku from your system can’t be longer than 30 seconds. Otherwise,
Heroku will terminate the calculation.
Salesforce CPQ executes the implemented methods for Product Search in the following order.
* 1.0 Constructor()
* 2.0 FOREACH(Search Input Field){
* 2.1 isFilterHidden()
* 2.1 getFilterDefaultValue()
* }
* 3.0 isSearchCustom (CUSTOM vs ENHANCED)
* IF(isCustom){
* 4.0 search()
* }
* ELSE{
* 4.0 getAdditionalSearchFilters()
* }
59
Salesforce CPQ Developer Guide Product Search Plugin
Walkthrough
1. The Constructor method can be called first, but it’s not required for implementation.
2. You’ll start off by entering a search field value in the FOREACH method. Salesforce CPQ calls this method for each filter value it
receives, then passes those values to the following methods:
• isFilterHidden: Hides the search filter from the Quote Line Editor if the Quote status is set to approved
• getFilterDefaultValue: Sets the field value for the initial search
3. Salesforce CPQ calls isSearchCustom to determine whether you’re using Custom or Enhanced searching.
4. If isSearchCustom returned True, Salesforce CPQ calls search(). This method gives you full control of the search query - you’ll build
the Select Clause and Where Clause manually, then build and perform the query.
5. If isSearchCustom returned False, Salesforce CPQ calls getAdditionalSearchFilters. This method appends a WHERE clause to the
existing SOQL query.
Method Samples
Each method below contains an implementation example. In each example, the SOQL query is from the price book entry table.Constructor
global class ExampleProductSearchPlugin implements SBQQ.ProductSearchPlugin{
/**
* Constructor. Not required for implementation
*/
global ExampleProductSearchPlugin(){
System.debug('METHOD CALLED: ExampleProductSearchPlugin Constructor');
}
isFilterHidden
Description
Determines the visibility of a filter in the UI. Salesforce CPQ calls this implemented method for each input.
Parameters
Return Values
Returns TRUE if the field should be hidden in the UI, FALSE otherwise.
Example
global Boolean isFilterHidden(SObject quote, String fieldName){
/*
// This would hide Product Code filter if Quote Status is Approved
return fieldName == 'ProductCode' && quote.SBQQ__Status__c. == 'Approved';
*/
return false;
}
60
Salesforce CPQ Developer Guide Product Search Plugin
getFilterDefaultValue
Description
Determines the input value for the initial search. Salesforce CPQ calls this implemented method for each input field.
Parameters
Return Values
Value to pass for this field in the initial search. NULL if none.
Example
global String getFilterDefaultValue(SObject quote, String fieldName){
System.debug('METHOD CALLED: getFilterDefaultValue');
/*
// This would set Product Family filter to Service if Quote Type is Quote
return (fieldName == 'Family' && quote.SBQQ__Type__c. == 'Quote') ? 'Service' : NULL;
*/
return NULL;
}
isSearchCustom
Description
Determines if the mode is CUSTOM or ENHANCED. CUSTOM = Full control of Query; ENHANCED = Append additional criteria to WHERE
clause.
Parameters
Return Values
TRUE for CUSTOM mode. FALSE for ENHANCED mode.
Example
global Boolean isSearchCustom(SObject quote, Map<String,Object> fieldValuesMap){
System.debug('METHOD CALLED: isSearchCustom');
/*
// This would use CUSTOM mode if a Search field for sorting was defined and used
return fieldValuesMap.get('Sort_By__c') != '';
*/
61
Salesforce CPQ Developer Guide Product Search Plugin
return true;
}
Return Values
String to be appended to WHERE clause. NULL if none.
Example
global String getAdditionalSearchFilters(SObject quote, Map<String,Object> fieldValuesMap){
if(fieldValuesMap.get('Family') == 'Hardware'){
additionalFilter = 'Product2.Inventory_Level__c > 3';
}
return additionalFilter;
*/
return NULL;
}
Return Values
62
Salesforce CPQ Developer Guide Product Search Plugin
List of Price Book Entries with Product2 external lookup field set
Example
global List<Price BookEntry> search(SObject quote, Map<String,Object> fieldValuesMap){
System.debug('METHOD CALLED: search');
//GET ALL POSSIBLE FILTER FIELDS FROM THE SEARCH FILTER FIELD SET
List<Schema.FieldSetMember> searchFilterFieldSetFields =
SObjectType.Product2.FieldSets.SBQQ__SearchFilters.getFields();
//GET ALL POSSIBLE FIELDS FROM THE SEARCH RESULTS FIELD SET
List<Schema.FieldSetMember> searchResultFieldSetFields =
SObjectType.Product2.FieldSets.SBQQ__SearchResults.getFields();
return pbes;
}
GUIDED SELLING
Salesforce CPQ executes the implemented methods for Guided Selling in the following order:
* 1.0 Constructor()
* 2.0 FOREACH(Search Input Field){
* 2.1 isInputHidden()
63
Salesforce CPQ Developer Guide Product Search Plugin
* 2.1 getInputDefaultValue()
* }
* 3.0 isSearchCustom (CUSTOM vs ENHANCED)
* IF(isCustom){
* 4.0 search()
* }
* ELSE{
* 4.0 getAdditionalSearchFilters()
* }
Walkthrough
1. The Constructor method can be called first, but it’s not required for implementation.
2. You’ll start off by entering a search field value in the FOREACH method. Salesforce CPQ calls this method for each filter value it
receives, then passes those values to the following methods:
3. isInputHidden: Hides the search filter from the Quote Line Editor if the Quote status is set to approved.
4. getInputDefaultValue: Sets the field value for the initial search.
5. Salesforce CPQ calls isSearchCustom to determine whether you’re using Custom or Enhanced searching.
6. If isSearchCustom returned True, Salesforce CPQ calls search(). This method gives you full control of the search query - you’ll build
the Select Clause and Where Clause manually, then build and perform the query.
7. If isSearchCustom returned False, Salesforce CPQ calls getAdditionalSearchFilters. This method appends a WHERE clause to the
existing SOQL query.
isInputHidden
Description
Determines the visibility of an Input in the UI when using Guided Selling. Salesforce CPQ calls this implemented method for each input.
Parameters
Return Values
Returns TRUE if the field should be hidden in the UI, FALSE otherwise
Example
global Boolean isInputHidden(SObject quote, String input){
System.debug('METHOD CALLED: isInputHidden');
/*
// This would hide an Input called 'Urgent Shipment' on Fridays.
return input == 'Urgent Shipment' && Datetime.now().format('F') == 5;
*/
return false;
}
getInputDefaultValue
Description
64
Salesforce CPQ Developer Guide Product Search Plugin
Determines the input value for the initial search. Salesforce CPQ calls this implemented method for each Quote Process Input.
Parameters
Return Values
Returns a value to pass for this field in the initial search. NULL if none.
Example
global String getInputDefaultValue(SObject quote, String input){
System.debug('METHOD CALLED: getInputDefaultValue');
return NULL;
}
isSearchCustom()
Description
Determines if the mode is CUSTOM or ENHANCED when using Guided Selling. CUSTOM = Full control of Query; ENHANCED = Append
additional criteria to WHERE clause.
Parameters
Return Values
TRUE for CUSTOM mode. FALSE for ENHANCED mode.
/**
global Boolean isSuggestCustom(SObject quote, Map<String,Object> inputValuesMap){
return true;
}
65
Salesforce CPQ Developer Guide Product Search Plugin
Return Values
String to be appended to WHERE clause. NULL if none.
Example
global String getAdditionalSearchFilters(SObject quote, Map<String,Object> fieldValuesMap){
if(fieldValuesMap.get('Family') == 'Hardware'){
additionalFilter = 'Product2.Inventory_Level__c > 3';
}
return additionalFilter;
*/
return NULL;
}
Return Values
List of Price Book Entries with Product2 external lookup field set
Example
global List<Price BookEntry> search(SObject quote, Map<String,Object> fieldValuesMap){
System.debug('METHOD CALLED: search');
//GET ALL POSSIBLE FILTER FIELDS FROM THE SEARCH FILTER FIELD SET
List<Schema.FieldSetMember> searchFilterFieldSetFields =
SObjectType.Product2.FieldSets.SBQQ__SearchFilters.getFields();
//GET ALL POSSIBLE FIELDS FROM THE SEARCH RESULTS FIELD SET
66
Salesforce CPQ Developer Guide External Configurator Plugins
List<Schema.FieldSetMember> searchResultFieldSetFields =
SObjectType.Product2.FieldSets.SBQQ__SearchResults.getFields();
return pbes;
}
67
Salesforce CPQ Developer Guide External Configurator Plugins
Important: We strongly recommend that you choose Dialog Window for the value of URL Target. Replace Window causes users
to lose all their work when the external configurator loads.
68
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
3. After configuring the JSON, use Salesforce CPQ’s postMessage method to send the JSON string back to the Salesforce CPQ app.
rpc.postMessage(JSON-string);
69
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
Note: Salesforce CPQ no longer provides support for Legacy Quote Calculator plugins. Check Available in: Salesforce CPQ
out the Javascript Quote Calculator Plugin for support and improved features. Winter ’16 and later
Note: The sample script assumes the Salesforce admin created custom fields True
Effective End Date and True Effective Term on the Quote Line object.
Example:
global class QCPWinter16Legacy2 implements SBQQ.QuoteCalculatorPlugin,
SBQQ.QuoteCalculatorPlugin2 {
/* This QCP examples calculates and stores the effective end date on each quote line,
as well as the effective term.
It also stores the max(effective end date) and max(effective term) on the Quote
object
*/
by the plugin and not in the Line Editor field set on the Quote Line
object
String.valueOf(SBQQ__QuoteLine__c.My_Field_API_Name__c)
*/
String.valueOf(SBQQ__QuoteLine__c.True_Effective_End_Date__c),
String.valueOf(SBQQ__QuoteLine__c.True_Effective_Term__c),
String.valueOf(SBQQ__Quote__c.True_Effective_End_Date__c),
String.valueOf(SBQQ__Quote__c.True_Effective_Term__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveStartDate__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveEndDate__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionTerm__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__DefaultSubscriptionTerm__c),
String.valueOf(SBQQ__Quote__c.SBQQ__SubscriptionTerm__c)
};
}
70
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
71
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
Date endDate =
(Date)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveEndDate__c));
if ((startDate != null) && (endDate != null)) {
/* Note: we are assuming that Subscription Term Unit is Month in the package
settings */
lineTerm = startDate.monthsBetween(endDate.addDays(1));
/* Note: we are assuming that Subscription Term Unit is Day in the package
settings */
// lineTerm = startDate.daysBetween(endDate.addDays(1));
} else {
lineTerm =
(Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionTerm__c));
if (lineTerm == null) {
lineterm =
(Decimal)quote.get(String.valueOf(SBQQ__Quote__c.SBQQ__SubscriptionTerm__c));
if (lineTerm == null) {
return
(Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__DefaultSubscriptionTerm__c));
}
}
}
return lineTerm;
}
Note: Salesforce CPQ no longer provides support for Legacy Quote Calculator plugins. Check Available in: Salesforce CPQ
out the Javascript Quote Calculator Plugin for support and improved features. Winter ’16 and later
Note: The sample script assumes the Salesforce admin created a custom field Component
Custom Total on the Quote Line object.
Example:
global class QCPWinter16Legacy implements SBQQ.QuoteCalculatorPlugin,
SBQQ.QuoteCalculatorPlugin2 {
72
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
by the plugin and not in the Line Editor field set on the Quote Line
object
String.valueOf(SBQQ__QuoteLine__c.My_Field_API_Name__c)
*/
String.valueOf(SBQQ__QuoteLine__c.Component_Custom_Total__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProratedListPrice__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__PriorQuantity__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__PricingMethod__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__DiscountScheduleType__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__Renewal__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__Existing__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionPricing__c)
};
}
if(parent != null) {
Decimal pComponentCustomTotal =
(Decimal)parent.get(String.valueOf(SBQQ__QuoteLine__c.Component_Custom_Total__c));
Decimal cListPrice =
(Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProratedListPrice__c));
Decimal cQuantity =
(Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Quantity__c));
Decimal cPriorQuantity =
(Decimal)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__PriorQuantity__c));
String cPricingMethod =
(String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__PricingMethod__c));
String cDiscountScheduleType =
(String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__DiscountScheduleType__c));
Boolean cRenewal =
(Boolean)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Renewal__c));
Boolean cExisting =
(Boolean)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Existing__c));
String cSubscriptionPricing =
(String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__SubscriptionPricing__c));
73
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
parent.put(SBQQ__QuoteLine__c.Component_Custom_Total__c,
pComponentCustomTotal);
}
}
}
74
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
return result;
}
75
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin
Note: Salesforce CPQ no longer provides support for Legacy Quote Calculator plugins. Check Available in: Salesforce CPQ
out the Javascript Quote Calculator Plugin for support and improved features. Winter ’16 and later
Example:
global class QCPForFindingLookupRecords implements
SBQQ.QuoteCalculatorPlugin, SBQQ.QuoteCalculatorPlugin2 {
global set<String> getReferencedFields() {
return new Set<String> {
String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProductCode__c),
String.valueOf(SBQQ__QuoteLine__c.SBQQ__Description__c)
};
}
}
for (SObject line : lines) {
String productCode =
(String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProductCode__c));
76
Salesforce CPQ Developer Guide Product Configuration Initializer for Guided Selling
public LF_ProductInitializerController() {
// Set "skip" to true to bypass the configuration page, or to false to on the
config page after the initializer has completed
skip = true;
77
Salesforce CPQ Developer Guide Product Search Executor for Guided Selling
1');
Decimal myInput2 = toInteger(ApexPages.currentPage().getParameters().get('Process
Input 2'));
78
Salesforce CPQ Developer Guide Document Store Plugin
To use an executor with a quote process, go to the quote process’s Product Search Executor field and enter c__ followed by the
Visualforce page’s name.
Example: Your organization sells laptop workstations. A guided selling prompt lets sales reps filter your product catalog by
memory, screen size, and type of processor. You could also make a simple product search executor plugin that further filters their
results by active products and products that aren’t bundle components.
Here’s a sample APEX controller for your plugin.
public with sharing class TWProductSearchController {
public Product2[] products {get; set;}
public String error {get; set;}
public TWProductSearchController() {
products = [SELECT Id FROM Product2 WHERE IsActive = true AND SBQQ__Component__c
= false];
}
}
79
Salesforce CPQ Developer Guide Custom Action Plugin
To create a custom action plugin, create a custom script record and enter your code in the Code field. Then, go to Salesforce CPQ package
settings an open the plugins tab. Enter the name of your custom script in the Custom Action Plugin field and save your changes.
80
Salesforce CPQ Developer Guide Salesforce CPQ Electronic Signature Plugin
Here’s a basic template for the plugin, without any additional code. You can use onBeforeCloneLine or onAfterCloneLine as needed.
export function onBeforeCloneLine(quote, clonedLines) {
return Promise.resolve();
}
String getSendButtonLabel();
}
81
INDEX
A L
Add products 22–23, 28 Legacy quote calculator plugin 69–70, 72, 76
Amend contracts 32–33, 36, 39
Apex 58, 69–70, 72, 76, 79 O
Option model 5
C
Calculate API 16 P
Calculator plugin 45 Page security plugin 43, 55, 58
Configuration attribute model 7, 13 Plugin 43
Configuration model 8–9, 11 Pricing API 14
contracts 32 Product API 26
CPQ 2–16, 22–23, 26, 28, 30, 32–33, 36, 38–40, 42, 67–69 Product model 10
CPQ apex 55, 58, 69–70, 72, 76, 79 Product search plugin 59
CPQ API 2–16, 26, 32–33, 36, 39–40, 42
CPQ API quickstart guide 2–13, 40, 42 Q
CPQ contracts 32 QCP 44–45, 48, 50, 52, 54
CPQ plugin 43, 55, 58, 69–70, 72, 76, 79, 81 Quote API 15–16
CPQ plugins 59 Quote calculator plugin 43
Quote Calculator Plugin 44–45, 48, 50, 52, 54
D Quote terms 30
Document API 12 QuoteProposal model 12
Document store plugin 79
S
E Salesforce calculator 45
Electronic signature plugin 43, 81 Salesforce CPQ 43–45, 48, 50, 52, 54–55, 58, 67–70, 72, 76, 79, 81
External configurator 67–69 Save API 15
F U
Feature model 6 User Interface API quickstart 14, 20, 22–23, 28, 30, 32–33, 36, 38–
39
J
JSForce 54 W
JSQCP 44–45, 48, 50, 52, 54 Web application 67–69
82