Loading
Salesforce now sends email only from verified domains. Read More
Table of Contents
Select Filters

          No results
          No results
          Here are some search tips

          Check the spelling of your keywords.
          Use more general search terms.
          Select fewer filters to broaden your search.

          Search all of Salesforce Help
          Use the Procedure Plan Framework

          Use the Procedure Plan Framework

          To ensure your procedures run in the correct order and pricing is applied consistently to a quote, create a new procedure plan.

          Required Editions

          Available in: Lightning Experience
          Available in: Enterprise, Unlimited, and Developer Editions of Revenue Cloud where Salesforce Pricing is enabled
          User Permissions Needed
          To create and update procedure plan definitions:

          Procedure Plan Access

          AND

          Salesforce Pricing Design Time User

          To create, update, and delete pricing procedures:

          Procedure Plan Access

          OR

          Salesforce Pricing Design Time User

          To use pricing procedures: Salesforce Pricing Run Time User
          To define, edit, delete, set security, and set version settings for Apex classes: Author Apex

          Imagine that you work at an airline and assist business travelers. Now, let’s book a flight! We want our pricing to reflect real-time market conditions and show prices in the customer's local currency.

          Before calculating the price, we’ll automatically fetch a dynamic base fare from an external system to ensure competitive pricing. We'll also apply the booking platform’s discounts and add a convenience fee.

          Once we build our procedure plan, we’ll create a quote. Then, based on the user's location (for example, India), we’ll convert the total price from USD to INR.

          Before you begin, ensure that you:

          • Have enabled Salesforce Pricing.
          • Have permissions to create procedures, procedure plans, products, and quotes.
          • Understand how to create pricing procedures. To learn more about pricing procedures, see Build Your Pricing Procedures Using Salesforce Pricing.
          • Understand and know how to use Context Definitions. To learn more about context definitions, see Context Definitions.
          • Use the same context definition across your pricing procedures and procedure plans.

          Procedure plan definitions can be complex, especially when they involve Apex classes and different pricing procedures. Follow these steps to create a quote and learn how to make your pricing dynamic and accurate without complicating the procedure itself.

          Turn on Procedure Plan Orchestration for Pricing

          1. From Setup, in the Quick Find box, enter Revenue Settings, then select Revenue Settings.
          2. Find and enable Procedure Plan Orchestration for Pricing.
          3. Find and enable Exclude Default and Sales Transaction Type Pricing Procedures.

          Create a Commercial Product

          1. From the App Launcher, find and select Products.
          2. Create a commercial product called Delhi - New York.
            To learn how to create a product, see Create Simple Products and ensure that the Product Record Type is set as Commercial.
          3. Add your product to the catalog and create a price book entry for it.
          4. From Setup, in the Quick Find box, search for and select Salesforce Pricing Setup.
          5. In the Sync Pricing Data section, click Sync.

          Build Your Pricing Procedure

          1. Create a pricing procedure and name it Flight Booking Pricing Procedure.
            To create a pricing procedure, follow the first 5 steps in Configure Your Pricing Procedure.
          2. Create constants. These constants will serve as placeholders for fixed values in your pricing procedure.
            Constant Name Data Type Default Value
            AdjType TEXT Percentage
            AdjValue NUMBER 5
            Override TEXT Override
            ConvFeeAdjType TEXT Amount
            ConvFeeAdjValue NUMBER -250
          3. Add the following elements.
            1. Pricing Setting
            2. List Price. Use the Price Book Entries V2 decision table.
            3. Manual Discounts. You’ll need to add three manual discount elements.
          4. The first Manual Discount element is added to calculate the dynamic base fare of the flight. Map these variables.
            • Input Variables
              • Adjustment Type: Override
              • Adjustment Value: PartnerUnitPrice
              • Quantity: LineItemQuantity
              • Input Unit Price: ListPrice
            • Output Variable
              • Net Unit Price: NetUnitPrice
          5. The second Manual Discount element is added to calculate the platform discount for the flight booking. Map these variables.
            • Input Variables
              • Adjustment Type: AdjType
              • Adjustment Value: AdjValue
              • Quantity: LineItemQuantity
              • Input Unit Price: PartnerUnitPrice
            • Output Variable
              • Net Unit Price: NetUnitPrice
          6. And the last Manual Discount element is added to calculate the convenience fee to charge your customer for the flight booking.
            • Input Variables
              • Adjustment Type: ConvFeeAdjType
              • Adjustment Value: ConvFeeAdjValue
              • Quantity: LineItemQuantity
              • Input Unit Price: NetUnitPrice
            • Output Variable
              • Net Unit Price: NetUnitPrice
          7. Set your preferences to view pricing information, profile access, and rank information.
          8. Save your procedure.
          9. Activate your procedure.

          Your procedure should look like this. For the sake of clarity, we’ve renamed each Manual Discount element to show the pricing calculation it will perform. To rename an element, click Edit iconand enter your desired name.

          Procedure Plan's pricing procedure

          Define Classes for Apex Hooks

          1. From Setup, in the Quick Find box, enter Apex, then select Apex Classes.
          2. Click New.
          3. The first Apex class is a prehook that fetches all Sales Transaction Items from the Pricing context and overrides their PartnerUnitPrice tag with a dynamic random base fare between 990 and 1200 before pricing execution from an external database.
            public class DynamicFlightBasePriceApex implements RevSignaling.SignalingApexProcessor {
            
                public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                    System.debug('Executing PREHOOK');
                    String contextId = request.ctxInstanceId;
                    Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                    
                    //Query SalesTransactionItem nodes
                    Map<String, Object> inputQueryItem = new Map<String, Object>{
                        'contextId' => contextId,
                            'tags' => new List<String>{ 'SalesTransactionItem' }
                    };
                    Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                    Map<String, Object> itemQueryResult = (Map<String, Object>)itemQueryOutput.get('queryResult');
                    List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
                    System.debug('QLI itemData=' + itemData);
            
                    // Generate random price between 990 and 1200
                    Decimal minPrice = 990;
                    Decimal maxPrice = 1200;
                    Decimal range = maxPrice - minPrice;
            
                    // Use Crypto.getRandomInteger() to generate a secure random number
                    Integer randomInt = Math.abs(Crypto.getRandomInteger());
                    Decimal randomPrice = minPrice + Math.mod(randomInt, range.intValue() + 1);
            
                    System.debug('Generated Random Price: ' + randomPrice);
                    
                    // STEP 3 - Build update list
                    List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
                    for (Object itemObj : itemData) {
                        Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                        List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                        System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
            			dataPath.remove(0); // Remove contextId
            			itemNodeUpdates.add(new Map<String, Object>{
                            'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                            'attributes' => new List<Object>{
                                new Map<String, Object>{
                                    'attributeName' => 'PartnerUnitPrice',
                                    'attributeValue' => randomPrice
                               }
                            }
                        });
                    }
                           
                    // STEP 4 - Submit context update
                    if (!itemNodeUpdates.isEmpty()) {
                        Map<String, Object> updateInput = new Map<String, Object>{
                            'contextId' => contextId,
                            'nodePathAndAttributes' => itemNodeUpdates
                        };
                        System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                        System.debug(JSON.serializePretty(updateInput));
                        industriesContext.updateContextAttributes(updateInput);
                    }
            
                    // Return the response
                    RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                    response.status = RevSignaling.TransactionStatus.SUCCESS;
                    response.message = 'Apex executed successfully with Random Price: ' + randomPrice;
                    return response;
                }
            }
            
          4. Create another Apex Class.
            This Apex class is a posthook that fetches the NetUnitPrice tag after pricing and converts it to the local currency, INR (Indian Rupees) using a simulated dynamic conversion rate between 85–86. It then updates the Sales Transaction Item description with the new INR fare, providing localized pricing visibility for Indian customers.
            public class ConvertFareToINRApex implements RevSignaling.SignalingApexProcessor {
            
                public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
                    System.debug('Executing POSTHOOK');
                    String contextId = request.ctxInstanceId;
                    Context.IndustriesContext industriesContext = new Context.IndustriesContext();
                    
                    //Query SalesTransactionItem nodes
                    Map<String, Object> inputQueryItem = new Map<String, Object>{
                        'contextId' => contextId,
                            'tags' => new List<String>{ 'SalesTransactionItem' }
                    };
                    Map<String, Object> itemQueryOutput = industriesContext.queryTags(inputQueryItem);
                    Map<String, Object> itemQueryResult = (Map<String, Object>)itemQueryOutput.get('queryResult');
                    List<Object> itemData = (List<Object>) itemQueryResult.get('SalesTransactionItem');
                    System.debug('QLI itemData=' + itemData);
                    
                    //Query NetUnitPrice nodes
                    Map<String, Object> inputQueryItem2 = new Map<String, Object>{
                        'contextId' => contextId,
                            'tags' => new List<String>{ 'NetUnitPrice' }
                    };
                    Map<String, Object> itemQueryOutput2 = industriesContext.queryTags(inputQueryItem2);
                    Map<String, Object> itemQueryResult2 = (Map<String, Object>)itemQueryOutput2.get('queryResult');
                    List<Object> itemData2 = (List<Object>) itemQueryResult2.get('NetUnitPrice');
                    System.debug('NetUnitPrice itemData=' + itemData2);
                    Map<String, Object> netUnitTagData = (Map<String, Object>) itemData2.get(0);
                    Decimal netUnitPrice = (Decimal) netUnitTagData.get('tagValue');
                    netUnitPrice = netUnitPrice.setScale(2);
                    System.debug('NetUnitPrice Tag Value=' + netUnitPrice);
            
                    // Generate random price between 85.00 and 86.00 with decimals
                    Decimal minPrice = 85.00;
                    Decimal maxPrice = 86.00;
                    Decimal range = maxPrice - minPrice; 
                    Integer randomInt = Math.abs(Crypto.getRandomInteger());
                    Integer randomDecimalInt = Math.mod(randomInt, 100);
                    Decimal randomFraction = Decimal.valueOf(randomDecimalInt) / 100;
                    Decimal usdToInrRate = minPrice + randomFraction;
                    if (usdToInrRate > maxPrice) { 
                        usdToInrRate = maxPrice;
                    }
                    
                    // Calculate NetUnitPrice in INR
                    Decimal fareInInr = (netUnitPrice * usdToInrRate).setScale(2);
                    
                    String fareMessage = 'The Flight fare in INR is: ' + fareInInr + 
                                 ' (USD ' + netUnitPrice + 
                                 ' at rate ' + usdToInrRate + ')';
                    System.debug(fareMessage);
                    
                    // STEP 3 - Build update list
                    List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
                    for (Object itemObj : itemData) {
                        Map<String, Object> itemNode = (Map<String, Object>) itemObj;
                        List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
                        System.debug('Full item dataPath: ' + JSON.serialize(dataPath));
            			dataPath.remove(0); // Remove contextId
            			itemNodeUpdates.add(new Map<String, Object>{
                            'nodePath' => new Map<String, Object>{ 'dataPath' => dataPath },
                            'attributes' => new List<Object>{
                                new Map<String, Object>{
                                    'attributeName' => 'SalesTrxnItemDescription',
                                    'attributeValue' => fareMessage
                               }
                            }
                        });
                    }
                        
                    
                    // STEP 4 - Submit context update
                    if (!itemNodeUpdates.isEmpty()) {
                        Map<String, Object> updateInput = new Map<String, Object>{
                            'contextId' => contextId,
                            'nodePathAndAttributes' => itemNodeUpdates
                        };
                        System.debug('--- PREHOOK: SUBMITTING CONTEXT UPDATE ---');
                        System.debug(JSON.serializePretty(updateInput));
                        industriesContext.updateContextAttributes(updateInput);
                    }
            
                    // Return the response
                    RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
                    response.status = RevSignaling.TransactionStatus.SUCCESS;
                    response.message = 'Apex executed successfully with Price in INR: ' + fareInInr;
                    return response;
                }
            }
            

          Configure a Custom Procedure Plan Definition

          1. From Setup, in the Quick Find box, search for and select Procedure Plan Definitions.
          2. Click New
          3. Specify these details.
            1. Title: Flight Booking Procedure Plan.
              Press Tab to autopopulate the Developer Name.
            2. Process Type: Revenue Cloud.
            3. Primary Object: Quote.
            4. Context Definition: <Context Definition Name>.
              You’ll need to choose a context definition that’s been designed for your use case. However, ensure that this is the same context definition you used when you built your pricing procedure.
            5. Save your changes.
          4. Open the newly created procedure plan definition record.
          5. To add the procedures that you want in the Procedure Plan Sections, select Add.
          6. Add the first section to get the dynamic base price of our flight. Specify these details.
            1. Type: Standard.
            2. Name: DynamicBasePriceApex.
            3. Section Type: Apex.
            4. Once the section is added, click Arrow icon and specify these details.
              1. Phases: Pricing
              2. Resolution Type: Default
              3. Apex: DynamicFlightBasePriceApex
              It should look something like this:
              Procedure Plan Section
          7. Similarly, add another section to perform the pricing calculation and override the base price with the price we generated using the Apex prehook. Specify these values.
            1. Type: Standard.
            2. Name: FlightPriceCalculation.
            3. Section Type: Pricing Procedure .
            4. Click Arrow icon and specify these details.
              1. Phases: Pricing
              2. Resolution Type: Default
              3. Procedure: Flight_Booking_Pricing_Procedure
          8. Finally, add a section to convert the price from USD to INR (US Dollar to Indian Rupee) by adding this Apex posthook for users whose Billing Country is set to India
            1. Type: Standard.
            2. Name: ConvertFareToINR.
            3. Section Type: Apex.
            4. Click Arrow icon and specify these details.
              1. Phases: Pricing
              2. Resolution Type: Rule-Based
              3. Condition Requirements: All Conditions Are Met (AND)
              4. Resource: Bill To Country
              5. Operator: Equals
              6. Output Value: India
              7. Apex: ConvertFareToINRApex
          9. Save and then, activate your procedure plan definition.

          Verify Your Procedure Plan Execution

          To verify if the procedure plan is executed in the order we set and the pricing is accurate, we’ll need to create a quote.

          1. Create a quote.
            In the Bill To Country field, enter India.
          2. Save your changes.
          3. Click Browse Catalogs and add the Delhi - New York product to the quote.
          4. Hover on the Net Unit Price value to see the price waterfall details.
            You’ll see the application of your procedure plan and the discounts and additions that you configured in your pricing procedure.
            Quote price waterfall
          5. To view the converted rate for your quote line item, click View iconin the quote line item row, and select View.
          6. The Line Item Description shows the localized price for your flight.
            Quote Line Item description
           
          Loading
          Salesforce Help | Article