0% found this document useful (0 votes)
163 views

CPQ Developer Guide

Uploaded by

Alberto Einstein
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
163 views

CPQ Developer Guide

Uploaded by

Alberto Einstein
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 86

Salesforce CPQ Developer

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

Chapter 1: Salesforce CPQ Developer Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


Get Started with Salesforce CPQ API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
CPQ API Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
CPQ Quote API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
CPQ Contract API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Generate Quote Document API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Service Router . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
CPQ API Quickstart Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Disable CPQ Triggers in Apex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Salesforce CPQ Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Javascript Quote Calculator Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Product Search Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
External Configurator Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Legacy Quote Calculator Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Product Configuration Initializer for Guided Selling . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Product Search Executor for Guided Selling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Document Store Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Custom Action Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Salesforce CPQ Electronic Signature Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
CHAPTER 1 Salesforce CPQ Developer Guide
In this chapter ... Start working with Salesforce CPQ API and plugins.

• Get Started with


Salesforce CPQ API
• Salesforce CPQ
Plugins

1
Salesforce CPQ Developer Guide Get Started with Salesforce CPQ API

Get Started with Salesforce CPQ API


Want to start working with Salesforce CPQ API? Check out the CPQ data models, types of API, and
EDITIONS
quickstart guides.
Available in: Salesforce CPQ
CPQ API Models Summer ’16 and later
CPQ API references several CPQ data models. To use the Salesforce CPQ API, create classes for
each data model in your org.
CPQ Quote API
Manage quotes and quoting actions with CPQ quote API.
CPQ Contract API
Use CPQ Contract API to amend and renew CPQ quotes.
Generate Quote Document API
Creates and saves a CPQ quote document.
Service Router
SBQQ.ServiceRouter is a global Apex class serving as a single entry point for all API calls. You can use it for calls made by Apex code,
Visualforce Remoting, or REST callouts.
CPQ API Quickstart Guide
Review examples of integrating Salesforce CPQ API with your platform.
Disable CPQ Triggers in Apex
You can manually disable Salesforce CPQ and Salesforce Billing application logic when you update records. This process is helpful
when you’re updating your own custom field. It’s also helpful when you update a record several times in one transaction and want
triggers to run only on the last iteration.

CPQ API Models


CPQ API references several CPQ data models. To use the Salesforce CPQ API, create classes for each
EDITIONS
data model in your org.
Available in: Salesforce CPQ
CPQ API QuoteLineModel Summer ’16 and later
The Quote Line model represents a quote line data model in Salesforce CPQ.
CPQ API QuoteLineGroupModel
The Quote Line Group model represents a quote line group data model in Salesforce CPQ.
CPQ API OptionModel
The Option model represents a product option data model in Salesforce CPQ.
CPQ API FeatureModel
The Feature model represents a product feature data model in Salesforce CPQ.
CPQ API ConfigAttributeModel
The ConfigAttribute model represents the configuration attribute object in Salesforce CPQ.
CPQ API ConfigurationModel
The Configuration model represents a bundle product in Salesforce CPQ.

2
Salesforce CPQ Developer Guide CPQ API Models

CPQ API ConstraintModel


The Constraint model represents the option constraint object in Salesforce CPQ.
CPQ API ProductModel
The Product model represents a product data model in Salesforce CPQ.
CPQ API QuoteModel
The Quote model represents a CPQ quote data model in Salesforce CPQ.
CPQ API QuoteProposalModel
The QuoteProposal model represents a quote document in Salesforce CPQ.
CPQ API QuoteTermModel
The QuoteTerm model represents the quote term object in Salesforce CPQ.

CPQ API QuoteLineModel


The Quote Line model represents a quote line data model in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
record SBQQ__QuoteLine__c The record that this model Summer ’16 and later
represents.

amountDiscountProrated Boolean Corresponds to


SBQQ__QuoteLine__c.ProrateAmountDiscount__c.

parentGroupKey Integer The unique key of this line’s


group, if this line is part of a
grouped quote.

parentItemKey Integer The unique key of this line’s


parent, if this line is part of a
bundle.

key Integer Each quote line and group has


a key that is unique amongst
all other keys in the same
quote.

upliftable Boolean True if this line is an MDQ


segment that can be uplifted
from a previous segment.

configurationType String Indicates the configuration type


of the product that this line
represents.

configurationEvent String Indicates the configuration


event of the product that this
line represents.

reconfigurationDisabled Boolean If true, this line cannot be


reconfigured.

3
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


descriptionLocked Boolean If true, this line’s description cannot be
changed.

productQuantityEditable Boolean If true, this line’s quantity cannot be


changed.

productQuantityScale Decimal The number of decimal places used for


rounding this line’s quantity.

dimensionType String The type of MDQ dimension that this line


represents.

productHasDimensions Boolean If true, the underlying product can be


represented as a multidimensional line.

targetCustomerAmount Decimal The unit price forwhich this quote line is


discounted.

targetCustomerTotal Decimal The customer amount for which this quote


line is discounted.

public class QuoteLineModel {


public SBQQ__QuoteLine__c record;
public Boolean amountDiscountProrated;
public Integer parentGroupKey;
public Integer parentItemKey;
public Integer key;
public Boolean upliftable;
public String configurationType;
public String configurationEvent;
public Boolean reconfigurationDisabled;
public Boolean descriptionLocked;
public Boolean productQuantityEditable;
public Decimal productQuantityScale;
public String dimensionType;
public Boolean productHasDimensions;
public Decimal targetCustomerAmount;
public Decimal targetCustomerTotal;
}

CPQ API QuoteLineGroupModel


The Quote Line Group model represents a quote line group data model in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
record SBQQ__QuoteLineGroup__c The record that this model Summer ’16 and later
represents.

4
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


netNonSegmentTotal Decimal The net total for all non-multidimensional
quote lines.

key Integer Each quote line and group has a key that is
unique amongst all other keys in the same
quote.

public class QuoteLineGroupModel {


public SBQQ__QuoteLineGroup__c record;
public Decimal netNonSegmentTotal;
public Integer key;
}

CPQ API OptionModel


The Option model represents a product option data model in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
record SBQQ__ProductOption__c The record that this model Summer ’16 and later
represents.

externalConfigurationData Map<String,String> Internal property for the


external configurator feature.

configurable Boolean Indicates whether the option is


configurable.

configurationRequired Boolean Indicates whether the


configuration of the option is
required.

quantityEditable Boolean Indicates whether the quantity


is editable. Editability is
determined by the quantity
and bundled fields on the
option record.

priceEditable Boolean Indicates whether the price is


editable. Editability is
determined by the price
editable field on the product
record and the bundled field
on the option record.

productQuantityScale Decimal Returns the value of the


quantity scale field for the
product being configured.

5
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


priorOptionExists Boolean Checks if this option is an asset on the
account that the quote is associated with.

dependentIds Set<Id> The option IDs that depend on this option.

controllingGroups Map<String,Set<Id>> The option IDs that this option depends on.

exclusionGroups Map<String,Set<Id>> The option IDs that this option is exclusive


with.

reconfigureDimensionWarning String Reconfigures the warning label for an option


with segments.

hasDimension Boolean Indicates whether this option has


dimensions or segments.

isUpgrade Boolean Indicates whether the product option is


related to an upgrade product.

dynamicOptionKey String Internal property for dynamic options.

public class OptionModel {


public SBQQ__ProductOption__c record;
public Map<String,String> externalConfigurationData;
public Boolean configurable;
public Boolean configurationRequired;
public Boolean quantityEditable;
public Boolean priceEditable;
public Decimal productQuantityScale;
public Boolean priorOptionExists;
public Set<Id> dependentIds;
public Map<String,Set<Id>> controllingGroups;
public Map<String,Set<Id>> exclusionGroups;
public String reconfigureDimensionWarning;
public Boolean hasDimension;
public Boolean isUpgrade;
public String dynamicOptionKey;
}

CPQ API FeatureModel


The Feature model represents a product feature data model in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
record SBQQ__ProductFeature__c The record that this model Summer ’16 and later
represents.

instructionsText String Instruction label for the feature.

6
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


containsUpgrades Boolean This feature is related to an upgrade
product.

public class FeatureModel {


public SBQQ__ProductFeature__c record;
public String instructionsText;
public Boolean containsUpgrades;
}

CPQ API ConfigAttributeModel


The ConfigAttribute model represents the configuration attribute object in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
name String Corresponds directly to Summer ’16 and later
SBQQ__ConfigurationAttribute__c.Name.

targetFieldName String Corresponds directly to


SBQQ__Confgi uratoi nAtrbi ute__cS.BQQ__TargetFeidl __c.

displayOrder Decimal Corresponds directly to


SBQQ__Confgi uratoi nAtrbi ute__cS.BQQ__DsipalyOrder__c.

columnOrder String Corresponds directly to


SBQQ__Congfiuratoi nAtrbi ute__cS.BQQ__Coul mnOrder__c.

required Boolean Corresponds directly to


SBQQ__Confgi uratoi nAtrbi ute__cS.BQQ__Requried__c.

featureId Id Corresponds directly to


SBQQ__ConfigurationAttribute__c.SBQQ__Feature__c.

position String Corresponds directly to


SBQQ__ConfigurationAttribute__c.SBQQ__Position__c.

appliedImmediately Boolean Corresponds directly to


SBQQ__CongfiuratoinAtbriute__cS.BQQ__Appeildm
I medaiteyl__c.

applyToProductOptions Boolean Corresponds directly to


SBQQ__CongifuraotinAtbriute__cS.BQQ__AppylToProductOpotins__c.

autoSelect Boolean Corresponds directly to


SBQQ__Confgi uratoi nAtrbi ute__cS.BQQ__AutoSeelct__c.

shownValues String[] Corresponds directly to


SBQQ__Congfiuratoi nAtrbi ute__cS.BQQ__ShownVaul es__c.

hiddenValues String[] Corresponds directly to


SBQQ__Congfi uratoi nAtrbi ute__cS.BQQ__Hdi denVaul es__c.

7
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


hidden Boolean Corresponds directly to
SBQQ__ConfigurationAttribute__c.SBQQ__Hidden__c.

noSuchFieldName String If no field with the target name exists, the


target name is stored here.

myId String Corresponds directly to


SBQQ__ConfigurationAttribute__c.Id.

public class ConfigAttributeModel {


public String name;
public String targetFieldName;
public Decimal displayOrder;
public String colmnOrder;
public Boolean required;
public Id featureId;
public String position;
public Boolean appliedImmediately;
public Boolean applyToProductOptions;
public Boolean autoSelect;
public String[] shownValues;
public String[] hiddenValues;
public Boolean hidden;
public String noSuchFieldName;
public Id myId;
}

CPQ API ConfigurationModel


The Configuration model represents a bundle product in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
configuredProductId Id The Product2.Id. Summer ’16 and later

optionId Id The
SBQQ__ProductOption__c.Id.

optionData SBQQ__ProductOption__c Editable data about the option,


such as quantity or discount.

configurationData SBQQ__ProductOption__c Stores the values of the


configuration attributes.

inheritedConfigurationData SBQQ__ProductOption__c Stores the values of the


inherited configuration
attributes.

optionConfigurations ConfigurationModel Stores the options selected on


this product.

8
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


configured Boolean Indicates whether the product has been
configured.

changedByProductActions Boolean Indicates whether a product action changed


the configuration of this bundle.

isDynamicOption Boolean Indicates whether the product was


configured using a dynamic lookup.

isUpgrade Boolean Queries whether this product is an upgrade.

disabledOptionIds Set<Id> The option IDs that are disabled.

hiddenOptionIds Set<Id> The option IDs that are hidden.

listPrice Decimal The list price.

priceEditable Boolean Indicates whether the price is editable.

validationMessages String[] Validation messages.

dynamicOptionKey String Internal property for dynamic options.

public class ConfigurationModel {


public Id configuredProductId;
public Id optionId;
public SBQQ__ProductOption__c optionData; // Editable data about the option in question,
such as quantity or discount
public SBQQ__ProductOption__c configurationData;
public SBQQ__ProductOption__c inheritedConfigurationData;
public ConfigurationModel[] optionConfigurations;
public Boolean configured;
public Boolean changedByProductActions;
public Boolean isDynamicOption;
public Boolean isUpgrade;
public Set<Id> disabledOptionIds;
public Set<Id> hiddenOptionIds;
public Decimal listPrice;
public Boolean priceEditable;
public String[] validationMessages;
public String dynamicOptionKey;
}

CPQ API ConstraintModel


The Constraint model represents the option constraint object in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
record SBQQ__OptionConstraint__c The record that this model Summer ’16 and later
represents.

9
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


priorOptionExists Boolean Checks if this option is an asset on the
account that the quote is associated with.

public class ConstraintModel {


public SBQQ__OptionConstraint__c record;
public Boolean priorOptionExists;
}

CPQ API ProductModel


The Product model represents a product data model in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
record Product2 The record that this model Summer ’16 and later
represents.

upgradedAssetId Id Provides a source for


SBQQ__QuoteLine__c.SBQQ__UpgradedAsset__c.

currencySymbol String The symbol for the currency in


use.

currencyCode String The ISO code for the currency


in use.

featureCategories String[] Allows users to sort product


features by category.

options OptionModel[] A list of all available options for


this product.

features FeatureModel All features available for this


product

configuration ConfigurationModel An object representing this


product’s current configuration.

configurationAttributes ConfigAttributeModel[] All configuration attributes


available for this product.

inheritedConfigurationAttributes ConfigAttributeModel[] All configuration attributes that


this product inherits from
ancestor products.

10
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


constraints ConstraintModel[] Option constraints on this product.

public class ProductModel {


public Product2 record;
public Id upgradedAssetId;
public String currencySymbol;
public String currencyCode;
public String[] featureCategories;
public OptionModel[] options;
public FeatureModel[] features;
public ConfigurationModel configuration;
public ConfigAttributeModel[] configurationAttributes;
public ConfigAttributeModel[] inheritedConfigurationAttributes;
public ConstraintModel[] constraints;
}

CPQ API QuoteModel


The Quote model represents a CPQ quote data model in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
record SBQQ__Quote__c The record that this model Summer ’16 and later
represents.

lineItems QuoteLineModel[] The lines that this quote


contains.

lineItemGroups QuoteLineGroupModel[] The groups that this quote


contains.

nextKey Integer The next key to use for new


groups or lines. To keep keys
unique, do not lower this value.

applyAdditionalDiscountLast Boolean Corresponds to the field


SBQQ__Quote__c.ApplyAdditionalDiscountLast__c.

applyPartnerDiscountFirst Boolean Corresponds to the field


SBQQ__Quote__c.ApplyPartnerDiscountFirst__c.

channelDiscountsOffList Boolean Corresponds to the field


SBQQ__Quote__c.ChannelDiscountsOffList__c.

customerTotal Decimal SBQQ__Quote__c.SBQQ__CustomerAmount__c


is a roll-up summary field, so its
accuracy is guaranteed only
after a quote has been saved.
In the meantime, its current

11
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


value is stored in customerTotal.

netTotal Decimal SBQQ__Quote__c.SBQQ__NetAmount__c


is a roll-up summary field, so its accuracy is
guaranteed only after a quote has been
saved. In the meantime, its current value is
stored in netTotal.

netNonSegmentTotal Decimal The net total for all non-multidimensional


quote lines.

public class QuoteModel {


public SBQQ__Quote__c record;
public QuoteLineModel[] lineItems;
public QuoteLineGroupModel[] lineItemGroups;
public Integer nextKey;
public Boolean applyAdditionalDiscountLast;
public Boolean applyPartnerDiscountFirst;
public Boolean channelDiscountsOffList;
public Decimal customerTotal;
public Decimal netTotal;
public Decimal netNonSegmentTotal;
}

CPQ API QuoteProposalModel


The QuoteProposal model represents a quote document in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
name String The document name. Summer ’16 and later

paperSize String The paper size. Possible values


are:
• Default
• Letter
• Legal
• A4
Defaults to Default.

outputFormat String The output format. Possible


values are:
• pdf
• word
Defaults to pdf.

quoteId Id The ID of your quote.

12
Salesforce CPQ Developer Guide CPQ API Models

Name Type Description


templateId Id The ID of your quote template.

language String The language code. Defaults to en_US.

public class QuoteProposalModel {


public String name;
public Id quoteId;
public Id templateId;
public String language;
public String outputFormat;
public String paperSize;
}

CPQ API QuoteTermModel


The QuoteTerm model represents the quote term object in Salesforce CPQ.
EDITIONS
Name Type Description Available in: Salesforce CPQ
id Id ID for the quote term. Summer ’16 and later

label String For quote templates with


multiple quote terms, this field
defines the order in which the
terms appear on the quote
document. Terms are ordered
from the lowest to highest
value.

locked Boolean Defines whether sales reps can


edit the quote term.
Corresponds directly to
SBQQ__QuoteTerm__c.SBQQ__Locked__c.

quoteId Id Allows users to relate the quote


term to a specific quote. Left
blank if the term applies to
multiple quotes. Corresponds
directly to
SBQQ__QuoteTerm__c.SBQQ__Quote__c.

standardTermId Id If the quote term was created


by clicking Modify Terms on the
quote, standardTermId
shows the original quote term’s
ID. Corresponds directly to
SBQQ__QuoteTerm__c.SBQQ__StandardTerm__c.

13
Salesforce CPQ Developer Guide CPQ Quote API

Name Type Description


type String Shows whether the term is standard,
modified, or custom. Unmodified terms
have a Type value of Standard. When a user
clicks Modify Terms on a quote and changes
the term, Salesforce CPQ creates a quote
term with a Type value of Modified.
Corresponds directly to
SBQQ__QuoteTerm__c.SBQQ__Type__c.

value String Value of the quote term’s Body field, or the


translated value of the Body field when
using CPQ translations. Quote Term Body
field value or the translated value when
using CPQ translations. Maximum 32,768
characters. Corresponds directly to
SBQQ__QuoteTerm__c.SBQQ__Body__c.

public class QuoteTermModel {


public String value;
public String type;
public Id standardTermId;
public Id quoteId;
public Boolean locked;
public String label;
public Id id;
}

CPQ Quote API


Manage quotes and quoting actions with CPQ quote API.
EDITIONS

Save Quote API Available in: Salesforce CPQ


The Save Quote API saves a CPQ quote. Summer ’16 and later

Calculate Quote API


The Calculate Quote API calculates the prices of a CPQ quote.
Read Quote API
The Read Quote API reads a quote from a CPQ quote ID.
Validate Quote API
Validate a CPQ quote and return any validation errors.
Add Products API
Receive a CPQ quote, product collection, and quote group key in a request, and return a Quote model with all provided products
added as quote lines.

14
Salesforce CPQ Developer Guide CPQ Quote API

Read Product API


The Read Product API takes the request’s product ID, pricebook ID, and currency code and returns a Product model. The Product
model loads the product from your catalog when the user requests it.
Create and Save Quote Proposal API
Create and save a CPQ quote proposal.
Quote Term Reader API
Retrieve quote terms for a quote.

Save Quote API


The Save Quote API saves a CPQ quote.
EDITIONS
Formats
JSON, Apex Available in: Salesforce CPQ
Summer ’16 and later
HTTP Method
POST
Authentication
Authorization: Bearer token
REQUEST
Name
quote
Type
QuoteModel
Required
Yes
Description
Representation of SBQQ__Quote__c data
RESPONSE
Type
QuoteModel
Description
Representation of SBQQ__Quote__c data

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

QuoteSaver saver = new QuoteSaver();


QuoteModel savedQuote = saver.save(quoteModel);
System.debug(savedQuote);

Calculate Quote API


The Calculate Quote API calculates the prices of a CPQ quote.
EDITIONS
Calculate Quote API performances handles calculations similarly to the speed of asynchronous
calculations in Salesforce CPQ. We do not recommend using it for processes that require instant Available in: Salesforce CPQ
calculation. Summer ’16 and later
The Calculate Quote API doesn’t use batches when creating and calculating quotes. Performance
may vary for quotes with large volumes of quote lines.

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 {

public void calculate(QuoteModel quote, String callbackClass) {


QuoteCalculatorContext ctx = new QuoteCalculatorContext(quote, callbackClass);
SBQQ.ServiceRouter.load('SBQQ.QuoteAPI.QuoteCalculator', null, JSON.serialize(ctx));

private class QuoteCalculatorContext {


private QuoteModel quote; //The quote and callbackClass properties are
called in the API code by the exact names seen here.
private String callbackClass; //Altering these property names will cause
calculator API calls to fail.

private QuoteCalculatorContext(QuoteModel quote, String callbackClass) {


this.quote = quote;
this.callbackClass = callbackClass;
}
}
}

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');

Read Quote API


The Read Quote API reads a quote from a CPQ quote ID.
EDITIONS
Formats
JSON, Apex Available in: Salesforce CPQ
Summer ’16 and later
HTTP Method
GET
Authentication
Authorization: Bearer token
REQUEST

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 {

public QuoteModel read(String quoteId) {


String quoteJSON = SBQQ.ServiceRouter.read('SBQQ.QuoteAPI.QuoteReader', quoteId);

return (QuoteModel) JSON.deserialize(quoteJSON, QuoteModel.class);


}
}

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);

Validate Quote API


Validate a CPQ quote and return any validation errors.
EDITIONS
Formats
JSON, Apex Available in: Salesforce CPQ
Winter ’19 and later
HTTP Method
PATCH
Authentication
Authorization: Bearer token
REQUEST
Parameter 1
Name: quote
Type: QuoteModel
Required: Yes
Description: Representation of SBQQ__Quote__c data.
RESPONSE
Type
String[]
Description
If the quote is valid, the array is empty. Otherwise, the array contains an item for each validation error.

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 {

public List<String> validate(QuoteModel quote) {


String res=SBQQ.ServiceRouter.load(’SBQQ.QuoteAPI.QuoteValidator’, null,
JSON.serialize(quote));
return (List<String>) JSON.deserialize(res, List<String>.class);
}
}

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

Validator validator = new Validator();


List<String> msgs = validator.validate(quoteModel);
System.debug(msgs);

Add Products API


Receive a CPQ quote, product collection, and quote group key in a request, and return a Quote
EDITIONS
model with all provided products added as quote lines.
Formats Available in: Salesforce CPQ
JSON, Apex Summer ’16 and later
HTTP Method
PATCH
Authentication
Authorization: Bearer token
REQUEST

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"

-H "Content-Type: application/json" -H "Authorization: Bearer token" -X PATCH -d


"@adderContext.json"

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 {

public QuoteModel add(QuoteModel quote, ProductModel[] products, Integer groupKey) {


AddProductsContext ctx = new AddProductsContext(quote, products, groupKey);
String quoteJSON = SBQQ.ServiceRouter.load('SBQQ.QuoteAPI.QuoteProductAdder', null,
JSON.serialize(ctx));
return (QuoteModel) JSON.deserialize(quoteJSON, QuoteModel.class);
}

private class AddProductsContext {


private QuoteModel quote;
private ProductModel[] products;
private Integer groupKey;
private final Boolean ignoreCalculate = true; //Must be hardcoded to true

private AddProductsContext(QuoteModel quote, ProductModel[] products, Integer


groupKey) {
this.quote = quote;
this.products = products;
this.groupKey = groupKey;
}
}
}

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

List<ProductModel> productModels = new List<ProductModel>();


productModels.add(productModel);
ProductAdder adder = new ProductAdder();
QuoteModel quoteWithProducts = adder.add(quoteModel, productModels, 0);
System.debug(quoteWithProducts);

Read Product API


The Read Product API takes the request’s product ID, pricebook ID, and currency code and returns
EDITIONS
a Product model. The Product model loads the product from your catalog when the user requests
it. Available in: Salesforce CPQ
Formats Summer ’16 and later
JSON, Apex
Special Access Rules: Users
HTTP Method must have read access to
PATCH the product2 object.

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 {

public ProductModel read(Id productId, Id pricebookId, String currencyCode) {


ProductReaderContext ctx = new ProductReaderContext(pricebookId, currencyCode);
String productJSON = SBQQ.ServiceRouter.load('SBQQ.ProductAPI.ProductLoader',
productId, JSON.serialize(ctx));
return (ProductModel) JSON.deserialize(productJSON, ProductModel.class);
}

private class ProductReaderContext {


private Id pricebookId;
private String currencyCode;

private ProductReaderContext(Id pricebookId, String currencyCode) {


this.pricebookId = pricebookId;
this.currencyCode = currencyCode;
}
}
}

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);

Create and Save Quote Proposal API


Create and save a CPQ quote proposal.
EDITIONS
Note: Salesforce CPQ doesn’t support attaching Additional Document records through API.
Available in: Salesforce CPQ
Formats Winter ’19 and later
JSON, Apex
HTTP Method
POST
Authentication
Authorization: Bearer token
REQUEST
Parameter 1
Name: name
Type: String

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 {

public String save(QuoteProposalModel context) {


return SBQQ.ServiceRouter.save('SBQQ.QuoteDocumentAPI.Save',
JSON.serialize(context));
}
}

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);

Quote Term Reader API


Retrieve quote terms for a quote.
EDITIONS
Formats
JSON, Apex Available in: Salesforce CPQ
Summer ’19 and later
HTTP Method
PATCH
Authentication
Authorization: Bearer token
REQUEST
Parameter 1
Name: quoteId
Type: Id
Required: Yes
Description: The quote record's ID
Parameter 2
Name: templateId

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 {

public List<QuoteTermModel> load(Id quoteId, Id templateId, String language) {


TermContext ctx = new TermContext(templateId, language);
String quoteTermsJSON = SBQQ.ServiceRouter.load('SBQQ.QuoteTermAPI.Load', quoteId,
JSON.serialize(ctx));
return (List<QuoteTermModel>) JSON.deserialize(quoteTermsJSON,
List<QuoteTermModel>.class);
}

private class TermContext {


private Id templateId;
private String language;

private TermContext(Id templateId, String language) {


this.templateId = templateId;
this.language = language;
}
}
}

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);

CPQ Contract API


Use CPQ Contract API to amend and renew CPQ quotes.
EDITIONS

Contract Amender API Available in: Salesforce CPQ


Receive a CPQ contract ID in a request, and return quote data for an amendment quote. Summer ’16 and later

Contract Renewer API


Receive a CPQ contract in a request, and return quote data for one or more renewal quotes.

32
Salesforce CPQ Developer Guide CPQ Contract API

Contract Amender API


Receive a CPQ contract ID in a request, and return quote data for an amendment quote.
EDITIONS
Service Provider Name
SBQQ.ContractManipulationAPI.ContractAmender Available in: Salesforce CPQ
Summer ’16 and later
Formats
JSON, Apex Special Access Rules: All of
HTTP Method these user permissions are
PATCH required.
• Insert and update on
Authentication
quote and opportunity
Authorization: Bearer token
objects
REQUEST • Read on quote,
Parameter 1 opportunity, and
Name: uid product2 objects
Type: String • Delete on quote object

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"

-H "Content-Type: application/json" -H "Authorization: Bearer token" -X PATCH

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);

return (QuoteModel) JSON.deserialize(quoteJSON, QuoteModel.class);


}
}

ContractAmender amender = new ContractAmender();


QuoteModel quote = amender.load('8001D000000AUlg'); // example id
System.debug(quote);

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

Contract Renewer API


Receive a CPQ contract in a request, and return quote data for one or more renewal quotes.
EDITIONS
Service Provider Name
SBQQ.ContractManipulationAPI.ContractRenewer Available in: Salesforce CPQ
Summer ’16 and later
Formats
JSON, Apex Special Access Rules: All of
HTTP Method these user permissions are
PATCH required.
• Insert and update on
Authentication
quote and opportunity
Authorization: Bearer token
objects
REQUEST • Read on quote,
Parameter 1 opportunity, and
Name: context product2 objects
Type: RenewalContext • Delete on quote object

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

public with sharing class ContractRenewer {


public QuoteModel[] load(String masterContractId, String context) {
String quotesJSON =
SBQQ.ServiceRouter.load('SBQQ.ContractManipulationAPI.ContractRenewer', masterContractId,
context);
return (QuoteModel[]) JSON.deserialize(quotesJSON, List<QuoteModel>.class);

37
Salesforce CPQ Developer Guide Generate Quote Document API

}
}

// example -> testInput = '{"masterContractId": "8001D000000AUlg", "renewedContracts":


[{"attributes":{"type":"Contract"},"Id":"8001D000000AUlg"},
{"attributes":{"type":"Contract"},"Id":"8001D000000AVGt"}]}';
String jsonInput = JSON.serialize(testInput);
ContractRenewer renewer = new ContractRenewer();
QuoteModel[] quotes = renewer.load(null, jsonInput);
System.debug(quotes);

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);

Generate Quote Document API


Creates and saves a CPQ quote document.
EDITIONS
Name
QuoteDocumentAPI.SaveProposal Available in: Salesforce CPQ
Winter ’19 and later
Note: “Proposal” refers to Salesforce CPQ’s Quote Document object.

Formats
JSON, Apex
HTTP Method
POST
Authentication
Authorization: Bearer token
Request

Table 1: Parameters
Name Type Required Definition
Name String No The document name

paperSize String No Values are Default, Letter, Legal,


A4. Defaults to “Default.”

outputFormat String No Values are PDF, Word. Defaults


to “PDF.”

quoteId ID Yes The quote’s ID

templateId ID Yes The quote template’s ID

language String No Defaults to “en_US.”

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"

-H "Content-Type: application/json" -H "Authorization: Bearer token" -X PATCH -d @data.json

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 {

public String save(QuoteProposalModel context) {


return SBQQ.ServiceRouter.save('SBQQ.QuoteDocumentAPI.Save',
JSON.serialize(context));
}
}

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

• global static String read(String reader, String uid)


• global static String load(String loader, String uid, String context)
• global static String save(String saver, String model)
Each method accepts the name of a service provider, such as SBQQ.ProductAPI.ProductLoader as its first parameter. This lets Salesforce
CPQ route the API request to the appropriate internal handler. The internal handler isn’t global and can’t be called directly by code
outside the Salesforce CPQ package.
Use the read() method only when it needs a simple unique ID as input. For example, you could use read() when querying for a
quote from the database. Provide the quote’s ID as the request and, and read() returns the QuoteModel object.
Use the load() method when you need more input information than a simple unique ID, but don’t need to change anything in the
database. For example, you could use load() if you wanted to load products for a given currency code and pricebook ID. The load()
method passes the unique ID as the second parameter. It then passes the extra information as a serialized JSON string in the third
parameter.
Use the save() method when you save a model to the database, such as when you save a quote. The save() method passes the
model as a serialized JSON string in the second parameter.

CPQ API Quickstart Guide


Review examples of integrating Salesforce CPQ API with your platform.
EDITIONS

Anonymous Apex Available in: Salesforce CPQ


Summer ’16 and later
This example reads a quote, adds a product, and saves the quote.

/**
* Note: this doesn’t perform a calculatation. Reference the calculate API to see how to
calculate a quote.
*/

//the Id of the quote


String quoteId = 'a0Wf100000J1vk1';

//the Id of the product to add to the quote


String productId = '01tj0000003P1SN';

//the Id of the pricebook for the quote and product being added
String pricebookId = '01sj0000003THhKAAW';

//the currency code


String currencyCode = 'USD';

//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

'{"quote" : ' + quoteModel + ', "products" : [' + productModel + '], "ignoreCalculate" :


true}');

//the JSON formatted String represeting the saved quote


String savedQuoteModel = SBQQ.ServiceRouter.save('SBQQ.QuoteAPI.QuoteSaver',
updatedQuoteModel);

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');

// login credentials to the org


var loginUrl = 'https://login.salesforce.com'; // if sandbox use https://test.salesforce.com
var username = '[email protected]';
var password = 'password';

// quote and product details


var quoteId = 'a0bA000000FW2o4';
var productId = '01tA0000005NsiA';
var pricebookId = '01tA0000005NsiA';
var currencyCode = 'USD';

// 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);
});

Disable CPQ Triggers in Apex


You can manually disable Salesforce CPQ and Salesforce Billing application logic when you update
EDITIONS
records. This process is helpful when you’re updating your own custom field. It’s also helpful when
you update a record several times in one transaction and want triggers to run only on the last Available in: Salesforce CPQ
iteration. Summer ’16 and later
To ensure that your org doesn’t run into operational issues when disabling triggers, test thoroughly.
Use the global Apex API TriggerControl—the same mechanism that Salesforce CPQ uses
internally—to manually disable triggers for the CPQ and Billing packages.

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;
}

Salesforce CPQ Plugins


Salesforce CPQ plugins let you add customized functionality to features within the Salesforce CPQ
EDITIONS
package.
Available in: All Salesforce
Javascript Quote Calculator Plugin CPQ Editions
Add extra functionality to the quote line editor in Salesforce CPQ with custom JavaScript code.
Seven available methods allow you to change how calculations are performed and manage
page-level security such as field visibility.
Product Search Plugin
Use the Product Search plugin to modify or add specificity to product filter searches and guided selling searches.
External Configurator Plugins
Developers can set up links to third-party web applications from within the configurator or quote line calculator. You can open links
through custom actions or with an automatic launch through a pluggable configurator.
Legacy Quote Calculator Plugin
Use Apex code to perform calculations within the CPQ quote line editor.
Product Configuration Initializer for Guided Selling
The product configuration initializer uses a custom user-provided APEX page to select options and set field values based on the
results of guided selling prompts. It works only for standard product option fields and not for configuration attributes or custom
product option fields.
Product Search Executor for Guided Selling
A Product Search Executor plugin filters the results of a guided selling prompt after a sales rep’s input. It consists of a Visualforce
controller and Visualforce page, which you can associate with a specific quote process within a guided selling configuration. It’s
useful if you want to add an extra level of guided selling product filtering beyond what sales reps can control.
Document Store Plugin
Use a CPQ document store plugin to store your quote documents as custom objects or in third-party integrations.

43
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin

Custom Action Plugin


A custom action plugin lets you run code before or after custom actions in Salesforce CPQ. Currently, custom action plugins support
only cloning actions.
Salesforce CPQ Electronic Signature Plugin
An electronic signature plugin lets developers add electronic signature functionality to their orgs. This is useful for organizations
who wish to streamline processes involving signatures, such as finalizing purchases and contracts.

Javascript Quote Calculator Plugin


Add extra functionality to the quote line editor in Salesforce CPQ with custom JavaScript code.
EDITIONS
Seven available methods allow you to change how calculations are performed and manage
page-level security such as field visibility. Available in: Salesforce CPQ
JavaScript code is saved in Salesforce CPQ as custom scripts. Winter ’16 and later

Quote Calculator Plugin Guidelines


Consider these key guidelines when planning scripts for the Quote Calculator Plugin.
Quote Calculator Plugin Methods
The Quote Calculator Plugin can reference these seven methods. You can export any, all, or none of them to achieve your desired
behavior.
Calculating True End Date and Subscription Term
Use JavaScript to make a Quote Line Calculator plugin that calculates values and stores maximum values for the custom quote line
fields True Effective End Date and True Effective Term.
Custom Package Total Calculation
The sample JavaScript script can be used in the Quote Line Calculator to calculate the total price for all components in a quote line
and then store that value in a custom field.
Find Lookup Records
The sample JavaScript script can be used in the Quote Line Calculator to query records within the plugin and use fields from those
records to set each quote line’s Description field.
Insert Records
The sample JavaScript script can be used in the Quote Line Calculator to insert records.
Javascript Page Security Plugin
Use Javascript functions to control field visibility and editability on your CPQ quotes.
Legacy Page Security Plugin (Apex)
The Salesforce CPQ Apex page security plugins let developers control field-level visibility or data entry mode in Salesforce CPQ
VisualForce pages.
Guidelines for Heroku in Quote Calculator Plugins
Salesforce CPQ quote calculator plugins call Heroku to perform asynchronous calculations. When you write a quote calculator plugin,
review important guidelines for working with the Heroku service.

44
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin

Quote Calculator Plugin Guidelines


Consider these key guidelines when planning scripts for the Quote Calculator Plugin.
EDITIONS

Promises Available in: Salesforce CPQ


Winter ’16 and later
A Promise is a built-in JavaScript object that allows for asynchronous programming in the browser.
Promises let you delay a certain action until another one has completed. Promises support a
.then(success, failure) method, where success is a function called when the promise resolves successfully, and failure is
a function called when the promise is rejected. If you want to do any asynchronous programming in the plugin, such as a server callout,
you must return a promise that resolves once that action is completed. This guarantees that calculation steps occur in the proper order.
If a method doesn’t require asynchronous behavior, you can return a promise that resolves immediately as return
Promise.resolve();. Promises can resolve to a value, which passes as a parameter to the .then() callbacks. You can use this
fact in your own code, but remember that the promises that these methods return don’t need to resolve to a value. Always directly
modify the quote and line models provided in the parameters.

QuoteModel and QuoteLineModel Types


The JavaScript calculator represents Quote__c and QuoteLine__c objects as QuoteModel and QuoteLineModel objects respectively.
You can access the underlying SObject through the .record property on both objects, which lets you reference fields by using their
API name. For example, you can reference a custom field SBQQ__MyCustomField__c on a given QuoteLineModel by accessing the
attribute record [”SBQQ__MyCustomField__c”]. You can also reference fields on related records. For example, if you want to reference
the field MyField__c on an account associated with a quote, access the record ["Account__r"]["MyField__c"].

Salesforce Field Types


You can change records stored in JavaScript Object Notation, or JSON. These records are serialized from your org. Number, Text, and
Boolean fields are all stored without any conversion, but you can convert any other type. For example, dates are represented as strings
of the format "YYYY-MM-DD." If you reference or change a field containing a date, you have to preserve that format.

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.

Quote Calculator Plugin Methods


The Quote Calculator Plugin can reference these seven methods. You can export any, all, or none
EDITIONS
of them to achieve your desired behavior.
Available in: Salesforce CPQ
onInit Winter ’16 and later

Param Type Description


{QuoteLineModel[]} quoteLineModels An array containing Javascript
representations of all lines in a quote.

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

Param Type Description


{QuoteModel} quoteModel Javascript representation of the quote you’re evaluating

(QuoteLineModel[]} quoteLineModels An array containing Javascript representations of all lines


in the quote

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

Param Type Description


{QuoteModel} quoteModel Javascript representation of the quote you’re evaluating

(QuoteLineModel[]} quoteLineModels An array containing Javascript representations of all lines


in the quote

The calculator calls this method before it evaluates price rules. Returns {promise}.
export function onBeforePriceRules(quoteModel, quoteLineModels) {
return Promise.resolve();
};

onAfterPriceRules

Param Type Description


{QuoteModel} quoteModel Javascript representation of the quote you’re evaluating

(QuoteLineModel[]} quoteLineModels An array containing Javascript representations of all lines


in the quote

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

Param Type Description


{QuoteModel} quoteModel Javascript representation of the quote you’re evaluating

(QuoteLineModel[]} quoteLineModels An array containing Javascript representations of all lines


in the quote

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.

Param Type Description


{FieldName} String Name of the field that will be hidden or made visible

(QuoteLineModelRecord} quoteLineModelRecord Javascript representation of the SObject record of line


you’re evaluating

The calculator calls this method after it completes a calculation. Returns {Boolean}

export function isFieldVisible(fieldName, quoteLineModelRecord) {

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

Param Type Description


{FieldName} String Name of the field that will be made read-only or editable

(QuoteLineModelRecord} quoteLineModelRecord Javascript representation of the SObject record of line


you’re evaluating

The calculator calls this method after it completes a calculation. Returns {Boolean}

export function isFieldEditable(fieldName, quoteLineModelRecord) {

if (fieldName == 'SBQQ__Description__c') {
return false;
}

return true;

};

Calculating True End Date and Subscription Term


Use JavaScript to make a Quote Line Calculator plugin that calculates values and stores maximum
EDITIONS
values for the custom quote line fields True Effective End Date and True Effective Term.
1. On the quote line object, create the following custom fields. Available in: Salesforce CPQ
Winter ’16 and later
a. A date field with the API name True_Effective_End_Date__c
b. A number field with the API name True_Effective_Term__c

2. Create a custom script record with a name of your choosing.


a. In the Quote Line Fields field, add True_Effective_End_Date__c and True_Effective_Term__c.
b. In the Code field, provide Javascript code that exports all of the methods that the calculator looks for and documents their
parameters and return types. Save your custom script and add its name to the Quote Calculator Plugin field in the Plugins tab
of Salesforce CPQ package settings. We've provided a sample custom script below.
export function onAfterCalculate(quote, lineModels) {

var maxEffectiveEndDate = null;


var maxEffectiveTerm = 0;
if (lineModels != null) {
lineModels.forEach(function (line) {
var trueEndDate = calculateEndDate(quote, line);
var trueTerm = getEffectiveSubscriptionTerm(quote, line);
if (maxEffectiveEndDate == null || (maxEffectiveEndDate < trueEndDate))
{
maxEffectiveEndDate = trueEndDate;
}
if (maxEffectiveTerm < trueTerm) {
maxEffectiveTerm = trueTerm;

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()
}

function calculateEndDate(quote, line) {


var sd = new Date(line.record["SBQQ__EffectiveStartDate__c"]);
var ed = new Date(line.record["SBQQ__EffectiveEndDate__c"]);
if (sd != null && ed != null ) {
ed = sd;
ed.setUTCMonth(ed.getUTCMonth() + getEffectiveSubscriptionTerm(quote, line));

ed.setUTCDate(ed.getUTCDate() - 1);
}
return ed;
}

function getEffectiveSubscriptionTerm(quote, line) {


if (line.record["SBQQ__EffectiveStartDate__c"] != null){
var sd = new Date(line.record["SBQQ__EffectiveStartDate__c"]);
}
if (line.record["SBQQ__EffectiveEndDate__c"] != null){
var ed = new Date(line.record["SBQQ__EffectiveEndDate__c"]);
}
if (sd != null && ed != null ) {
ed.setUTCDate(ed.getUTCDate() + 1);
return monthsBetween(sd, ed);
} else if (line.SubscriptionTerm__c != null) {
return line.SubscriptionTerm__c;
} else if (quote.SubscriptionTerm__c != null) {
return quote.SubscriptionTerm__c;
} else {
return line.DefaultSubscriptionTerm__c;
}
}

/**
* 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

var dateIso = date.toISOString();

// Replace everything after the T with an empty string


return dateIso.replace(new RegExp('[Tt].*'), "");
}

function monthsBetween(/*Date*/ startDate, /*Date*/ endDate) {


if(startDate != null && endDate != null ){
// If the start date is actually after the end date, reverse the arguments and
multiply the result by -1
if (startDate > endDate) {
return -1 * this.monthsBetween(endDate, startDate);
}
var result = 0;
// Add the difference in years * 12
result += ((endDate.getUTCFullYear() - startDate.getUTCFullYear()) * 12);
// Add the difference in months. Note: If startDate was later in the year than
endDate, this value will be
// subtracted.
result += (endDate.getUTCMonth() - startDate.getUTCMonth());
return result;
}
return 0;
}

Custom Package Total Calculation


The sample JavaScript script can be used in the Quote Line Calculator to calculate the total price
EDITIONS
for all components in a quote line and then store that value in a custom field.
This sample JavaScript code exports all of the methods that the calculator looks for, and documents Available in: Salesforce CPQ
their parameters and return types. Winter ’16 and later

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;
});
}
};

export function onAfterCalculate(quoteModel, quoteLines) {


if (quoteLines != null) {
quoteLines.forEach(function (line) {
var parent = line.parentItem;
if (parent != null) {
var pComponentCustomTotal = parent.record["Component_Custom_Total__c"] ||

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 || '';

var cTotalPrice = getTotal(cListPrice, cQuantity, cPriorQuantity,


cPricingMethod, cDiscountScheduleType, cRenewal, cExisting, cSubscriptionPricing,
cListPrice);
pComponentCustomTotal += cTotalPrice;

parent.record["Component_Custom_Total__c"] = pComponentCustomTotal;
}
});
}
};

function getTotal(price, qty, priorQty, pMethod, dsType, isRen, isExist, subPricing,


listPrice) {
if ((isRen === true) && (isExist === false) && (priorQty == null)) {
// Personal note: In onAfterCalculate, we specifically make sure that priorQuantity
can't be null.
// So isn't this loop pointless?
return 0;
} else {
return price * getEffectiveQuantity(qty, priorQty, pMethod, dsType, isRen, isExist,
subPricing, listPrice);
}
}

function getEffectiveQuantity(qty, priorQty, pMethod, dsType, isRen, exists, subPricing,


listPrice) {
var delta = qty - priorQty;

if (pMethod == 'Block' && delta == 0) {


return 0;
} else if (pMethod == 'Block') {
return 1;
} else if (dsType == 'Slab' && (delta == 0 || (qty == 0 && isRen == true))) {
return 0;
} else if (dsType == 'Slab') {
return 1;
} else if (exists == true && subPricing == '' && delta < 0) {
return 0;
} else if (exists == true && subPricing == 'Percent Of Total' && listPrice != 0 &&
delta >= 0) {
return qty;
} else if (exists == true) {
return delta;

51
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin

} else {
return qty;
}
}

Find Lookup Records


The sample JavaScript script can be used in the Quote Line Calculator to query records within the
EDITIONS
plugin and use fields from those records to set each quote line’s Description field.
Each version of the JavaScript code samples exports all of the methods that the calculator will look Available in: Salesforce CPQ
for, and documents their parameters and return types. Winter ’16 and later

Javascript

export function onAfterCalculate(quote, lines, conn) {


if (lines.length > 0) {
var productCodes = [];
lines.forEach(function(line) {
if (line.record['SBQQ__ProductCode__c']) {
productCodes.push(line.record['SBQQ__ProductCode__c']);
}
});
if (productCodes.length) {
var codeList = "('" + productCodes.join("', '") + "')";
/*
* conn.query() returns a Promise that resolves when the query completes.
*/
return conn.query('SELECT Id, SBQQ__Category__c, SBQQ__Value__c FROM SBQQ__LookupData__c
WHERE SBQQ__Category__C IN ' + codeList)
.then(function(results) {
/*
* conn.query()'s Promise resolves to an object with three attributes:
* - totalSize: an integer indicating how many records were returned
* - done: a boolean indicating whether the query has completed
* - records: a list of all records returned
*/
if (results.totalSize) {
var valuesByCategory = {};
results.records.forEach(function(record) {
valuesByCategory[record.SBQQ__Category__c] = record.SBQQ__Value__c;
});
lines.forEach(function(line) {
if (line.record['SBQQ__ProductCode__c']) {
line.record['SBQQ__Description__c'] =
valuesByCategory[line.record['SBQQ__ProductCode__c']] || '';
}
});
}
});
}
}

52
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin

return Promise.resolve();
}

Javascript Using Method-Chaining


This plugin uses method-chaining style to construct the query, which is useful when you want to dynamically construct your queries.
/**
* Created by jfeingold on 9/27/16.
*/
export function onAfterCalculate(quote, lines, conn) {
if (lines.length) {
var codes = [];
lines.forEach(function(line) {
var code = line.record['SBQQ__ProductCode__c'];
if (code) {
codes.push(code);
}
});
if (codes.length) {
var conditions = {
SBQQ__Category__c: {$in: codes}
};
var fields = ['Id', 'Name', 'SBQQ__Category__c', 'SBQQ__Value__c'];
/*
* Queries can also be constructed in a method-chaining style.
*/
return conn.sobject('SBQQ__LookupData__c')
.find(conditions, fields)
.execute(function(err, records) {
if (err) {
return Promise.reject(err);
} else {
var valuesByCategory = {};
records.forEach(function(record) {
valuesByCategory[record.SBQQ__Category__c] = record.SBQQ__Value__c;
});
lines.forEach(function(line) {
if (line.record['SBQQ__ProductCode__c']) {
line.record['SBQQ__Description__c'] =
valuesByCategory[line.record['SBQQ__ProductCode__c']] || '';
}
});
}
});
}
}
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

export function onAfterCalculate(quote, lines, conn) {


if (lines.length) {
var codes = [];
lines.forEach(function(line) {
var code = line.record['SBQQ__ProductCode__c'];
if (code) {
codes.push(code);
}
});
if (codes.length) {
var conditions = {
SBQQ__Category__c: {$in: codes}
};
var fields = ['Id', 'Name', 'SBQQ__Category__c', 'SBQQ__Value__c'];
return conn.sobject('SBQQ__LookupData__c')
.find(conditions, fields)
.execute(function(err, records) {
console.log(records);
if (err) {
return Promise.reject(err);
} else {
var valuesByCategory = {};
records.forEach(function(record) {
valuesByCategory[record.SBQQ__Category__c] = record.SBQQ__Value__c;
});
var newRecords = [];
lines.forEach(function(line) {
var code = line.record['SBQQ__ProductCode__c'];
var desc = line.record['SBQQ__Description__c'];
if (code && desc && !valuesByCategory[code]) {
newRecords.push({
SBQQ__Category__c: code,
SBQQ__Value__c: line.record['SBQQ__Description__c']
});
}
});
if (newRecords.length) {
return conn.sobject('SBQQ__LookupData__c')
.create(newRecords, function(err, ret) {
console.log(ret);
});
}
}
});
}

54
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin

}
return Promise.resolve();
}

Javascript Page Security Plugin


Use Javascript functions to control field visibility and editability on your CPQ quotes.
EDITIONS
The Javascript Page Security plugin supports four functions. The functions isFieldVisible
and isFieldEditable are available starting in Salesforce CPQ Summer '15 and control quote Available in: Salesforce CPQ
line field visibility and editability. The functions isFieldVisibleForObject and Summer '15 and later
isFieldEditableForObject are available starting in Salesforce CPQ Summer '19 and can
control field visibility and editability for both quote fields and quote line fields. When a method
using one of these functions returns False, Salesforce CPQ locks or hides the chosen fields. The fields are unchanged if the method returns
Null or True.

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.

Table 3: isFieldVisible (Summer '15)


Parameter Type Definition
fieldname string If isFieldVisible returns False, this
quote line field is hidden.

line SObject The quote line object

conn Object Methods access jsforce through the optional


parameter conn.

Table 4: isFieldEditable (Summer '15)


Parameter Type Definition
fieldname string If isFieldEditable returns False, this
quote line field is locked from edits.

line SObject The quote line object

conn Object Methods access jsforce through the optional


parameter conn.

55
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin

Table 5: isFieldVisibleForObject (Summer '19)


Parameter Type Definition
fieldName String if isFieldVisibleForObject
returns False, this field is hidden.

quote or line SObject The object containing the field that you're
evaluating to determine whether
fieldName is visible.

conn Object Methods access jsforce through the optional


parameter conn.

objectName String The object that contains fieldName. If


your second parameter is quote, use
Quote__c. If your second parameter is
quoteline, use QuoteLine__c.
Leave this parameter undefined to target
the same field on the quote and the quote
line.

Table 6: isFieldEditableForObject (Summer '19)


Parameter Type Definition
fieldName String if isFieldEditableForObject
returns False, this field is locked from edits.

quote or line SObject The object containing the field that you're
evaluating to determine whether
fieldName is editable.

conn Object Methods access jsforce through the optional


parameter conn.

objectName String The object that contains fieldName. If


your second parameter is quote, use
Quote__c. If your second parameter is
quoteline, use QuoteLine__c.
Leave this parameter undefined to target
the same field on the quote and the quote
line.

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;
}
}

if (objectName === 'QuoteLine__c' && fieldName === 'SBQQ__MarkupRate__c') {


if (line.SBQQ__DistributorDiscount__c > 10) {

return false;
}
}
}

57
Salesforce CPQ Developer Guide Javascript Quote Calculator Plugin

Legacy Page Security Plugin (Apex)


The Salesforce CPQ Apex page security plugins let developers control field-level visibility or data
EDITIONS
entry mode in Salesforce CPQ VisualForce pages.

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;
}

public Boolean isFieldEditable(String pageName, Schema.SObjectField field, SObject


record) {
return null;
}

public Boolean isFieldVisible(String pageName, Schema.SObjectField field) {


return null;
}

public Boolean isFieldVisible(String pageName, Schema.SObjectField field, SObject


record) {
if ((pageName == 'EditLines') && (record instanceof SBQQ__QuoteLine__c)) {
SBQQ__QuoteLine__c line = (SBQQ__QuoteLine__c)record;
if ((line.SBQQ__Bundle__c == true) && (field !=
SBQQ__QuoteLine__c.SBQQ__ProductName__c)) {
return false;
}
}
return null;
}
}

Guidelines for Heroku in Quote Calculator Plugins


Salesforce CPQ quote calculator plugins call Heroku to perform asynchronous calculations. When you write a quote calculator plugin,
review important guidelines for working with the Heroku service.

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.

Product Search Plugin


Use the Product Search plugin to modify or add specificity to product filter searches and guided
EDITIONS
selling searches.
You can choose from two modes that control how the plugin modifies base Salesforce CPQ product Available in: All Salesforce
searches. CPQ Editions
• Enhanced: Add more parameters to the WHERE clause of the product search's existing SOQL
query.
• Custom: Completely replace the product search's query logic with your own.
Salesforce CPQ applies the modified search immediately upon entering the product search screen, so the initial group of searchable
products is already filtered.
You can configure the Product Search plugin to filter a product search based on certain parameters when users enter their own search
queries. For example, in Product Search, you could configure the plugin to return all search results in descending order from the most
recent Last Ordered Date. When the user enters the Product Search, the products returned in the search results are shown from the most
recent Last Ordered Date. Users can further filter through the Product Search filter panel, if necessary. We'll return to this example in
the use case.
Product search plugins can use only a subset of Quote fields by default. If you can't pass a field to your product search, or if it passes as
null, you must instead pull it with a SOQL query using the ID passed with the quote model.
Plugin methods vary slightly between Product Search and Guided Selling. However, the different methods for each (isFilterHidden vs.
inInputHidden & getFilterDefaultValue vs. getInputDefaultValue) effectively perform the same function.

Note: Date fields are returned as strings in the yyyy-mm-dd format.

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

Param Type Description

quote SBQQ__Quote__c Current quote object

fieldName String API Name of the Product2 Field

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

Param Type Description

quote SBQQ__Quote__c Current quote object

fieldName String API Name of the Product2 field

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

Param Type Description

quote SBQQ__Quote__c Current quote object

fieldValuesMap Map<String,Object> Map of search criteria. Key is Product2 API


Name. Value is Value.

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;
}

getAdditionalSearchFilters() [Called when isSearchCustom = FALSE]


Description
Appends an extra WHERE clause text when using ENHANCED mode. SOQL query is from the Price Book entry table.
Parameters

Param Type Description

Quote SBQQ__Quote__c Current Quote Object

fieldValuesMap Map<String,Object> Map of search criteria. Key is Product2 API


Name. Value is Value.

Return Values
String to be appended to WHERE clause. NULL if none.
Example
global String getAdditionalSearchFilters(SObject quote, Map<String,Object> fieldValuesMap){

System.debug('METHOD CALLED: getAdditionalSearchFilters');


/*
// This would add an extra inventory filter if the family is Hardware
String additionalFilter = NULL;

if(fieldValuesMap.get('Family') == 'Hardware'){
additionalFilter = 'Product2.Inventory_Level__c > 3';
}

return additionalFilter;
*/
return NULL;
}

search() [Called when isSearchCustom = TRUE]


Definition
Override the entire search when using CUSTOM mode. Product2 fields in the Search Results field set should be set.
Parameters

Param Type Description

quote SBQQ__Quote__c Current Quote Object

fieldValuesMap Map<String,Object> Map of search criteria. Key is Product2 API


Name. Value is Value. Only contains Keys for
non-NULL values

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();

//BUILD THE SELECT STRING


String selectClause = 'SELECT ';

for(Schema.FieldSetMember field : searchResultFieldSetFields){


selectClause += 'Product2.' + field.getFieldPath() + ', ';
}
selectClause += 'Id, UnitPrice, Price Book2Id, Product2Id, Product2.Id';

//BUILD THE WHERE CLAUSE


String whereClause = '';

for(Schema.FieldSetMember field : searchFilterFieldSetFields){


if(!fieldValuesMap.containsKey(field.getFieldPath())){
continue;
}

if(field.getType() == Schema.DisplayType.String || field.getType() ==


Schema.DisplayType.Picklist){
whereClause += 'Product2.' + field.getFieldPath() + ' LIKE \'%' +
fieldValuesMap.get(field.getFieldPath()) + '%\' AND ';
}
}

whereClause += 'Price Book2Id = \'' + quote.get('SBQQ__Price Book__c') + '\'';

//BUILD THE QUERY


String query = selectClause + ' FROM Price BookEntry WHERE ' + whereClause;

//DO THE QUERY


List<Price BookEntry> pbes = new List<Price BookEntry>();
pbes = Database.query(query);

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

Param Type Description

quote SBQQ__Quote__c Current quote object

input String Name of the Quote Process Input

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

Param Type Description

quote SBQQ__Quote__c Current quote object

input String Name of the Quote Process Input

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

Param Type Description

quote SBQQ__Quote__c Current quote object

inputValuesMap Map<String,Object> Map of search criteria. Key is Process Input


Name. Value is Value. Only contains keys for
non-Null values. Includes additional key
"qpid", for the Quote Process ID

Return Values
TRUE for CUSTOM mode. FALSE for ENHANCED mode.
/**
global Boolean isSuggestCustom(SObject quote, Map<String,Object> inputValuesMap){
return true;
}

getAdditionalSearchFilters() [Called when isSearchCustom = FALSE]


Description
Appends an extra WHERE clause text when using Guided Selling in ENHANCED mode. SOQL query is from the Price Book entry table.
Parameters

Param Type Description

65
Salesforce CPQ Developer Guide Product Search Plugin

Quote SBQQ__Quote__c Current Quote Object

fieldValuesMap Map<String,Object> Map of search criteria. Key is Product2 API


Name. Value is Value

Return Values
String to be appended to WHERE clause. NULL if none.
Example
global String getAdditionalSearchFilters(SObject quote, Map<String,Object> fieldValuesMap){

System.debug('METHOD CALLED: getAdditionalSearchFilters');


/*
// This would add an extra inventory filter if the family is Hardware
String additionalFilter = NULL;

if(fieldValuesMap.get('Family') == 'Hardware'){
additionalFilter = 'Product2.Inventory_Level__c > 3';
}

return additionalFilter;
*/
return NULL;
}

search() [Called when isSearchCustom = TRUE]


Definition
Override the entire search when using in CUSTOM mode. Product2 Fields in the Search Results field set should be set.
Parameters

Param Type Description

quote SBQQ__Quote__c Current Quote Object

fieldValuesMap Map<String,Object> Map of search criteria. Key is Product2 API


Name. Value is Value. Only contains Keys for
non-NULL values

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();

//BUILD THE SELECT STRING


String selectClause = 'SELECT ';

for(Schema.FieldSetMember field : searchResultFieldSetFields){


selectClause += 'Product2.' + field.getFieldPath() + ', ';
}
selectClause += 'Id, UnitPrice, Price Book2Id, Product2Id, Product2.Id';

//BUILD THE WHERE CLAUSE


String whereClause = '';

for(Schema.FieldSetMember field : searchFilterFieldSetFields){


if(!fieldValuesMap.containsKey(field.getFieldPath())){
continue;
}

if(field.getType() == Schema.DisplayType.String || field.getType() ==


Schema.DisplayType.Picklist){
whereClause += 'Product2.' + field.getFieldPath() + ' LIKE \'%' +
fieldValuesMap.get(field.getFieldPath()) + '%\' AND ';
}
}

whereClause += 'Price Book2Id = \'' + quote.get('SBQQ__Price Book__c') + '\'';

//BUILD THE QUERY


String query = selectClause + ' FROM Price BookEntry WHERE ' + whereClause;

//DO THE QUERY


List<Price BookEntry> pbes = new List<Price BookEntry>();
pbes = Database.query(query);

return pbes;
}

External Configurator Plugins


Developers can set up links to third-party web applications from within the configurator or quote
EDITIONS
line calculator. You can open links through custom actions or with an automatic launch through a
pluggable configurator. Available in: Salesforce CPQ
Salesforce CPQ provides two ways to access an external configurator from the quote line editor. Winter ’16 and later
• Create a custom action that launches a URL. When a sales rep clicks the custom action, Salesforce
CPQ launches the external configurator.
• Define an external configurator in Salesforce CPQ package settings. Then, enable external configuration on any product that you
want sales reps to use within that configurator. When a sales rep adds or configures that product in the quote line editor, Salesforce
CPQ launches the external configurator. This feature is available as of Salesforce CPQ Summer ’17.

67
Salesforce CPQ Developer Guide External Configurator Plugins

Set Up an External Configurator to Launch from a Custom Action


Create a custom action that launches a non-Salesforce CPQ configurator.
Set Up a Web Application for Integration
Integrate with easyXDM to transfer data between your web application and Salesforce CPQ.
Set Up External Configurators for Automatic Launch
Allow users to launch a non-Salesforce CPQ configurator automatically from within the quote line editor. Salesforce CPQ captures
the external configurator’s values and applies relevant settings or changes back to the Salesforce CPQ configurator.

Set Up an External Configurator to Launch from a Custom Action


Create a custom action that launches a non-Salesforce CPQ configurator.
EDITIONS
You may have to add the following layouts and values.
Available in: Salesforce CPQ
• Add the Page and URL Target fields to the custom action page layout.
Winter ’16 and later
• Add the Popup value to the custom action’s URL Target field.
• Add a label that represents your external configurator’s name to the custom action’s Label field.
1. From your Custom Actions tab, click New.
2. From the Label field, choose GIS.
3. From the Page field, choose Product Configurator.
4. From the URL Target field, choose Popup.
5. In the URL field, add a URL for a custom website that uses a secure https protocol.
6. Save your changes.

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.

Set Up a Web Application for Integration


Integrate with easyXDM to transfer data between your web application and Salesforce CPQ.
EDITIONS
1. Add the easyXDM library to your web application. You can find the library at
http://easyxdm.net/wp/. Available in: Salesforce CPQ
Winter ’18 and later
2. Use the easyXDM.RPC constructor with the following configuration. Set the configuration’s keys
to postMessage.
var rpc = new easyXDM.Rpc({},{
remote: {
postMessage: {} // This method is defined in SteelBrick
},
local: {
postMessage: function(message) {
// Do something with the JSON string that is received from SteelBrick.
}
}
});

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);

Set Up External Configurators for Automatic Launch


Allow users to launch a non-Salesforce CPQ configurator automatically from within the quote line
EDITIONS
editor. Salesforce CPQ captures the external configurator’s values and applies relevant settings or
changes back to the Salesforce CPQ configurator. Available in: Salesforce CPQ
1. From Setup, enter Installed Packages, and then select Installed Packages. Winter ’18 and later
2. Find the Salesforce CPQ package and click Configure.
3. Select the Additional Settings tab.
4. In the External Configurator URL field, enter the URL for your external configurator.
5. Optional: On the Additional Settings page, you can also select the Third Party Configurator field. When active, Salesforce CPQ
launches the external configurator so it takes up your entire screen. Any action that closes the close the external configurator (cancel,
save, clicking X on your screen) redirects to the page that launched the external configurator.
6. Find the products that you want to launch the external configurator when a sales rep configures them.
a. Select the Externally Configurable field on each product record.
b. Make sure each product’s Configuration Type field has a value of Required.
The external configurator launches when a user clicks the wrench next to a configurable bundle, or when the user adds a product
where a configuration event is required. If you set the configuration type to Allowed, the external configurator launches only
when a sales rep selects the wrench icon next to a configurable bundle.

Legacy Quote Calculator Plugin


Use Apex code to perform calculations within the CPQ quote line editor.
EDITIONS
Note: Salesforce CPQ no longer provides support for Legacy Quote Calculator plugins. Check
out the Javascript Quote Calculator Plugin for support and improved features. Available in: All Salesforce
CPQ Editions
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.

Calculating True End Date and Subscription Term


The sample JavaScript script can be used in the Quote Line Calculator to calculate values and store maximum values for the custom
quote line fields True Effective End Date and True Effective Term.
Custom Package Total Calculation
The sample Apex class calculates the total price for all components in a quote line and then stores that value in a custom field.
Find Lookup Records
The sample Apex class can be used in the Legacy Quote Line Calculator to query records within the plugin and use fields from those
records to set each quote line’s Description field.

69
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin

Calculating True End Date and Subscription Term


The sample JavaScript script can be used in the Quote Line Calculator to calculate values and store
EDITIONS
maximum values for the custom quote line fields True Effective End Date and True Effective Term.

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
*/

/* NOTE: the getReferencedFields method is no longer required if you use the


ReferencedFields field set on
the Quote Line object.
This field set must be created as it's not a managed one.
NOTE: if you need to access Quote fields, you can create the ReferencedFields
field set on the Quote object as well.
NOTE: if you do not use the getReferencedFields method, you can remove
SBQQ.QuoteCalculatorPlugin2 from the class declaration.
*/
global Set<String> getReferencedFields() {
return new Set<String>{
/* Note: add fields using the following format - Only add fields referenced

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

global void onBeforePriceRules(SObject quote, SObject[] lines) {


}

global void onAfterPriceRules(SObject quote, SObject[] lines) {


}

global void onBeforeCalculate(SObject quote, SObject[] lines) {


}

global void onAfterCalculate(SObject quote, SObject[] lines) {


Date maxEffectiveEndDate = null;
Decimal maxEffectiveTerm = 0;
for(SObject line : lines) {
Date trueEndDate = calculateEndDate(quote, line);
Decimal trueTerm = getEffectiveSubscriptionTerm(quote, line);
if(maxEffectiveEndDate == null || maxEffectiveEndDate < trueEndDate) {
maxEffectiveEndDate = trueEndDate;
}
if(maxEffectiveTerm < trueTerm) {
maxEffectiveTerm = trueTerm;
}
line.put(SBQQ__QuoteLine__c.True_Effective_End_Date__c, trueEndDate);
line.put(SBQQ__QuoteLine__c.True_Effective_Term__c, trueTerm);
}
quote.put(SBQQ__Quote__c.True_Effective_End_Date__c, maxEffectiveEndDate);
quote.put(SBQQ__Quote__c.True_Effective_Term__c, maxEffectiveTerm);
}

global void onInit(SObject[] lines) {


}

private Date calculateEndDate(SObject quote, SObject line) {


Date startDate =
(Date)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveStartDate__c));
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 */
endDate = startDate.addMonths(getEffectiveSubscriptionTerm(quote,
line).intValue()).addDays(-1);
/* Note: we are assuming that Subscription Term Unit is Day in the package
settings */
// endDate = startDate.addDays(getEffectiveSubscriptionTerm(line).intValue()
- 1);
}
return endDate;
}

private Decimal getEffectiveSubscriptionTerm(SObject quote, SObject line) {


Decimal lineTerm = null;
Date startDate =
(Date)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__EffectiveStartDate__c));

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;
}

Custom Package Total Calculation


The sample Apex class calculates the total price for all components in a quote line and then stores
EDITIONS
that value in a custom field.

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 {

/* NOTE: the getReferencedFields method is no longer required if you use the


ReferencedFields field set on
the Quote Line object.
This field set must be created as it's not a managed one.
NOTE: if you need to access Quote fields, you can create the ReferencedFields
field set on the Quote object as well.
NOTE: if you do not use the getReferencedFields method, you can remove
SBQQ.QuoteCalculatorPlugin2 from the class declaration.
*/
global Set<String> getReferencedFields() {

72
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin

return new Set<String>{


/* Note: add fields using the following format - Only add fields referenced

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)
};
}

global void onBeforePriceRules(SObject quote, SObject[] lines) {


}

global void onAfterPriceRules(SObject quote, SObject[] lines) {


}

global void onBeforeCalculate(SObject quote, SObject[] lines) {


}

global void onAfterCalculate(SObject quote, SObject[] lines) {


for(SObject line : lines) {
SObject parent =
line.getSObject(SBQQ__QuoteLine__c.SBQQ__RequiredBy__c.getDescribe().getRelationshipName());

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

pComponentCustomTotal = (pComponentCustomTotal == null) ? 0 :


pComponentCustomTotal;

cListPrice = (cListPrice == null) ? 0 : cListPrice;


cQuantity = (cQuantity == null) ? 1 : cQuantity;
cPriorQuantity = (cPriorQuantity == null) ? 0 : cPriorQuantity;
cPricingMethod = (cPricingMethod == null) ? 'List' : cPricingMethod;
cDiscountSCheduleType = (cDiscountSCheduleType == null) ? '' :
cDiscountSCheduleType;
cRenewal = (cRenewal == null) ? false : cRenewal;
cExisting = (cExisting == null) ? false : cExisting;
cSubscriptionPricing = (cSubscriptionPricing == null) ? '' :
cSubscriptionPricing;

Decimal cTotalPrice = getTotal(cListPrice, cQuantity, cPriorQuantity,


cPricingMethod, cDiscountScheduleType, cRenewal, cExisting, cSubscriptionPricing,
cListPrice);
pComponentCustomTotal += cTotalPrice;

parent.put(SBQQ__QuoteLine__c.Component_Custom_Total__c,
pComponentCustomTotal);

}
}
}

global void onInit(SObject[] lines) {


for(SObject line : lines) {
line.put(SBQQ__QuoteLine__c.Component_Custom_Total__c, 0);
}
}

private Decimal getTotal(Decimal price, Decimal quantity, Decimal priorQuantity,


String pricingMethod, String discountScheduleType, Boolean renewal, Boolean existing,
String subscriptionPricing, Decimal ListPrice) {
price = (price == null) ? 0 : price;
renewal = (renewal == null) ? false : renewal;
existing = (existing == null) ? false : existing;

if(renewal == true && existing == false && priorQuantity == null) {


return 0;
} else {
return price * getEffectiveQuantity(quantity, priorQuantity, pricingMethod,
discountScheduleType, renewal, existing, subscriptionPricing, listPrice);
}

private Decimal getEffectiveQuantity(Decimal quantity, Decimal priorQuantity,


String pricingMethod, String discountScheduleType, Boolean renewal, Boolean existing,
String subscriptionPricing, Decimal ListPrice) {
Decimal result = 0;
Decimal deltaQuantity = 0;

74
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin

quantity = (quantity == null) ? 0 : quantity;


priorQuantity = (priorQuantity == null) ? 0 : priorQuantity;
pricingMethod = (pricingMethod == null) ? '' : pricingMethod;
discountScheduleType = (discountScheduleType == null) ? '' :
discountScheduleType;
subscriptionPricing = (subscriptionPricing == null) ? '' : subscriptionPricing;

renewal = (renewal == null) ? false : renewal;


existing = (existing == null) ? false : existing;
listPrice = (listPrice == null) ? 0 : listPrice;

deltaQuantity = quantity - priorQuantity;

if(pricingMethod == 'Block' && deltaQuantity == 0) {


result = 0;
} else {
if(pricingMethod == 'Block') {
result = 1;
} else {
if(discountScheduleType == 'Slab' && (deltaQuantity == 0 || (quantity
== 0 && renewal == true))) {
result = 0;
} else {
if(discountScheduleType == 'Slab') {
result = 1;
} else {
if(existing == true && subscriptionPricing == '' && deltaQuantity
< 0) {
result = 0;
} else {
if(existing == true && subscriptionPricing == 'Percent Of
Total' && listPrice != 0 && deltaQuantity >= 0) {
result = quantity;
} else {
if(existing == true) {
result = deltaQuantity;
} else {
result = quantity;
}
}
}
}
}
}
}

return result;
}

75
Salesforce CPQ Developer Guide Legacy Quote Calculator Plugin

Find Lookup Records


The sample Apex class can be used in the Legacy Quote Line Calculator to query records within
EDITIONS
the plugin and use fields from those records to set each quote line’s Description field.

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)
};
}

global void onInit(SObject[] lines) {}

global void onBeforeCalculate(SObject quote, SObject[] lines)


{}

global void onBeforePriceRules(SObject quote, SObject[] lines)


{}

global void onAfterPriceRules(SObject quote, SObject[] lines)


{}

global void onAfterCalculate(SObject quote, SObject[] lines)


{
if (!lines.isEmpty()) {
String[] productCodes = new String[0];
for (SObject line : lines) {
String productCode =
(String)line.get(String.valueOf(SBQQ__QuoteLine__c.SBQQ__ProductCode__c));

if (productCode != null && !productCode.isWhitespace()) {


productCodes.add(productCode);
}
}
SBQQ__LookupData__c[] ds = [SELECT Id, SBQQ__Category__c,
SBQQ__Value__c FROM SBQQ__LookupData__c WHERE SBQQ__Category__c
IN :productCodes];
if (!ds.isEmpty()) {
Map<String,String> valuesByCategory = new
Map<String,String>();
for (SBQQ__LookupData__c d : ds) {
valuesByCategory.put(d.SBQQ__Category__c, d.SBQQ__Value__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

if (productCode != null && !productCode.isWhitespace()) {


line.put(String.valueOf(SBQQ__QuoteLine__c.SBQQ__Description__c),
valuesByCategory.get(productCode));
}
}
}
}
}
}

Product Configuration Initializer for Guided Selling


The product configuration initializer uses a custom user-provided APEX page to select options and set field values based on the results
of guided selling prompts. It works only for standard product option fields and not for configuration attributes or custom product option
fields.
A product configuration initializer consists of a Visualforce controller and Visualforce page. To use the initializer on all of your org’s quote
processes, go to the Product Configuration Initializer field in Salesforce CPQ line editor package settings and enter c__ followed by the
Visualforce page’s name. To use an initializer on a specific quote process, go to the quote process’s Product Configuration Initializer field
and enter c__ followed by the Visualforce page’s name. Product configuration initializers on quote process records override the
package-level product configuration initializer.

Example: Sample Visualforce controller:

public with sharing class LF_ProductInitializerController {


public Product2[] products {get; set;}
public Boolean skip {get; set;}
Map<String,SBQQ__ProductOption__c> optionsByCode = new
Map<String,SBQQ__ProductOption__c>();

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;

// Retrieve product (bundle)


String pidsStr = ApexPages.currentPage().getParameters().get('pids');
String[] pids = pidsStr.split(',');
products = [SELECT Id, Family, (SELECT SBQQ__OptionalSKU__r.ProductCode,
SBQQ__Quantity__c, SBQQ__Selected__c FROM SBQQ__Options__r) FROM Product2 WHERE Id IN
:pids];
for (SBQQ__ProductOption__c opt : products[0].SBQQ__Options__r) {
optionsByCode.put(opt.SBQQ__OptionalSKU__r.ProductCode, opt);
}

String myInput1 = ApexPages.currentPage().getParameters().get('Process Input

77
Salesforce CPQ Developer Guide Product Search Executor for Guided Selling

1');
Decimal myInput2 = toInteger(ApexPages.currentPage().getParameters().get('Process
Input 2'));

// Perform any logic you want here

// Then select options in the bundle, for example:


if (myInput1 == 'ABC') {
selectOption('MyProductOption1', myInput2);
} else {
selectOption('MyProductOption2', 1)
}
}

private Decimal toInteger(String value) {


return String.isBlank(value) ? 0 : Decimal.valueOf(value);
}

private void selectOption(String code, Decimal qty) {


optionsByCode.get(code).SBQQ__Selected__c = (qty > 0);
optionsByCode.get(code).SBQQ__Quantity__c = qty;
}
}

Example: Sample Visualforce page:


<apex:page controller="LF_ProductInitializerController" contentType="text/xml"
showHeader="false" sidebar="false">
<products skipConfiguration="{!skip}">
<apex:repeat var="product" value="{!products}">
<product id="{!product.Id}">
<apex:repeat var="opt" value="{!product.SBQQ__Options__r}">
<option id="{!opt.Id}"
selected="{!opt.SBQQ__Selected__c}"
quantity="{!ROUND(opt.SBQQ__Quantity__c, 0)}"/>
</apex:repeat>
</product>
</apex:repeat>
</products>
</apex:page>

Product Search Executor for Guided Selling


A Product Search Executor plugin filters the results of a guided selling prompt after a sales rep’s input. It consists of a Visualforce controller
and Visualforce page, which you can associate with a specific quote process within a guided selling configuration. It’s useful if you want
to add an extra level of guided selling product filtering beyond what sales reps can control.

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];
}
}

And here’s a sample Visualforce page.


<apex:page controller="TWProductSearchController" contentType="text/xml"
showHeader="false" sidebar="false">
<products error="{!error}">
<apex:repeat var="product" value="{!products}">
<product id="{!product.Id}"/>
</apex:repeat>
</products>
</apex:page>

Document Store Plugin


Use a CPQ document store plugin to store your quote documents as custom objects or in third-party
EDITIONS
integrations.
Available in: All Salesforce
Table 7: storeDocument Method
CPQ Editions
Param Type Description
quote SObject (SBQQ__Quote__c) Quote record information from
the Salesforce CPQ quote.

document SObject The quote document record


(SBQQ__QuoteDocument__c) from Salesforce CPQ.

content Blob Represents the actual PDF or


Word file contents.

Example: Here's a sample document store plugin.


public class TestDocumentStorePlugin implements SBQQ.DocumentStorePlugin {

public void storeDocument(SObject quote, SObject document, Blob content) {


// Custom document saving logic goes here.
}

79
Salesforce CPQ Developer Guide Custom Action Plugin

// Reserved for future use


public Boolean isQuoteDocumentSaved() {
return true;
}

// Reserved for future use


public SObject[] listDocuments(SObject quote) {
return null;
}

Custom Action Plugin


A custom action plugin lets you run code before or after custom actions in Salesforce CPQ. Currently, custom action plugins support
only cloning actions.
A custom action plugin can call either the onBeforeCloneLine method or the onAfterCloneLine method so that you can
evaluate and modify a quote line before or immediately after the cloning process. These methods accept the following parameters.

Table 8: Parameters for onBeforeCloneLine and onAfterCloneLine


Parameter Type Definition
quote QuoteModel A representation of the quote object.

clonedLines Object Properties:


clonedLines
Available with onAfterCloneLine. An
array of new QuoteLineModels created
from the clone action. When using
onBeforeCloneLine, this property is
undefined.
originalLines
Available with onBeforeCloneLine and
onAfterCloneLine. An array of
QuoteLineModels for the original quote
lines that the user is cloning.
You can use the cloneLines parameter to
change fields on the old and new quote
lines.

conn Object A jsforce connection.

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();
}

Salesforce CPQ Electronic Signature Plugin


An electronic signature plugin lets developers add electronic signature functionality to their orgs.
EDITIONS
This is useful for organizations who wish to streamline processes involving signatures, such as
finalizing purchases and contracts. Available in: All Salesforce
Example: CPQ Editions

global virtual interface ElectronicSignaturePlugin {


void send(QuoteDocument__c[] documents);

void updateStatus(QuoteDocument__c[] documents);

void revoke(QuoteDocument__c[] documents);

String getSendButtonLabel();
}

global interface ElectronicSignaturePlugin2 extends


ElectronicSignaturePlugin {
Boolean isSendButtonEnabled();
}

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

You might also like