---
schema_version: "1"
id: d2bc41931b3ae19197080c3c80e8e724d9d2b248783ed58e62a32b05a503f5a1
title: Customize B2B Store Pricing with Apex Hooks
collection: admin
version: current
locale: en-us
portal_url: https://docs.salesforce.com/admin/current/en-us/commerce/264-0-0/comm_customize_pricing_apex_hooks.md
canonical_url: https://help.salesforce.com/s/articleView?id=commerce.comm_customize_pricing_apex_hooks.htm&release=264.0.0&type=5
source_site: help.salesforce.com
content_type: documentation
content_hash: 160450d99a3c195bde951c692d85c797f9fb9849956537335e50c185a16a9aaa
description: To support unique pricing scenarios, add custom Apex logic to your pricing procedure plans. You can use Apex hooks to apply custom business logic that modifies the pricing context before or after your pricing procedure runs. Apex hooks are custom code that run at specific points in the pricing procedure plan.
products:
  - Commerce
  - B2B Commerce
release: "264"
---
# Customize B2B Store Pricing with Apex Hooks

To support unique pricing scenarios, add custom Apex logic to your pricing procedure plans. You can use Apex hooks to apply custom business logic that modifies the pricing context before or after your pricing procedure runs. Apex hooks are custom code that run at specific points in the pricing procedure plan.

### Required Editions

<table class="slds-table slds-table_bordered slds-max-medium-table_stacked slds-m-bottom_small edition"><colgroup><col></colgroup><tbody><tr class=""><td class="slds-cell-wrap" style="vertical-align:top;"><a href="https://help.salesforce.com/s/articleView?id=commerce.comm_editions.htm&amp;language=en_US" target="_blank">View supported editions</a>.</td></tr></tbody></table>

[Which Salesforce Commerce Product Do I Have?](https://help.salesforce.com/s/articleView?id=commerce.comm_what_product.htm&language=en_US)

1. From Setup, in the Quick Find box, enter `Apex`, and select **Apex Classes**.
2. Select **New** to create an Apex class.
3. In the class editor, enter the class definition. See the sample classes for Apex hooks provided in this topic.
4. After you create your Apex class, add it as an Apex section in your procedure plan definition. From Setup, in the Quick Find box, enter `Procedure Plan Definitions`, and select **Procedure Plan Definitions**.
5. Select the procedure plan that you want to modify.
6. In the Procedure Plan Sections area, click **Add**.
7. Select **Standard** type.
8. Enter a name for your section.
9. For Section Type, select **Apex**.
10. In the section details, set Phases to **Pricing**, Resolution Type to **Default**, and select the Apex class you created.
11. Click **Save**.
12. If you added multiple sections, click **Manage Sections**. Drag the sections to set their exact execution order relative to your pricing procedure.

![Sample Classes for Apex Hooks](https://sf-zdocs-cdn-prod.zoominsoftware.com/tdta-commerce-comm-264-0-0-production-enus/d863e308-482a-4dfa-8d20-2a94737d394a/images/icon_example.png)

Sample Classes for Apex Hooks

Apex prehook: This example demonstrates an Apex prehook for attribute-driven pricing at the SalesTransactionItem node.

```
global class ApexDmlAttributePreHook implements RevSignaling.SignalingApexProcessor {
    public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
        System.debug('Executing PREHOOK');
        String contextId = request.ctxInstanceId;
        Context.IndustriesContext industriesContext = new Context.IndustriesContext();

        // STEP 1 - 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');

        // STEP 2 - Build update list for items with ProductName = 'Home Office Starter'
        List<Map<String, Object>> itemNodeUpdates = new List<Map<String, Object>>();
        for (Object itemObj : itemData) {
            Map<String, Object> itemNode = (Map<String, Object>) itemObj;
            Map<String, Object> tagMap = (Map<String, Object>) itemNode.get('tagValue');
            List<Object> dataPath = (List<Object>) itemNode.get('dataPath');
            String productName = null;

            if (tagMap != null && tagMap.containsKey('ProductName')) {
                productName = (String) ((Map<String, Object>) tagMap.get('ProductName')).get('tagValue');
            }

            if (productName == 'Home Office Starter') {
                System.debug('Matched Home Office Starter item: ' + JSON.serialize(dataPath));
                // Remove root contextId from dataPath
                dataPath.remove(0);
                itemNodeUpdates.add(new Map<String, Object>{
                    'nodePath' => new Map<String, Object>{
                        'dataPath' => dataPath
                    },
                    'attributes' => new List<Object>{
                        new Map<String, Object>{
                            'attributeName' => 'ProductCustom__c',
                            'attributeValue' => 'DISCOUNTAPPLICABLE'
                        }
                    }
                });
            }
        }

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

        RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
        response.status = RevSignaling.TransactionStatus.SUCCESS;
        return response;
    }
}
```

Apex posthook: This example demonstrates an Apex posthook to validate and selectively override pricing attributes.

```
global class ApexDmlListPricePostHook implements RevSignaling.SignalingApexProcessor {
    public RevSignaling.TransactionResponse execute(RevSignaling.TransactionRequest request) {
        System.debug('Executing POSTHOOK - Update ListPrice');
        String contextId = request.ctxInstanceId;
        Context.IndustriesContext industriesContext = new Context.IndustriesContext();

        // STEP 1 - 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');

        // STEP 2 - 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');

            // Remove root contextId as required by updateContextAttributes
            dataPath.remove(0);
            itemNodeUpdates.add(new Map<String, Object>{
                'nodePath' => new Map<String, Object>{
                    'dataPath' => dataPath
                },
                'attributes' => new List<Object>{
                    new Map<String, Object>{
                        'attributeName'  => 'ListPrice',
                        'attributeValue' => 20.0
                    }
                }
            });
        }

        // STEP 3 - Submit context update
        if (!itemNodeUpdates.isEmpty()) {
            Map<String, Object> updateInput = new Map<String, Object>{
                'contextId' => contextId,
                'nodePathAndAttributes' => itemNodeUpdates
            };
            System.debug('--- POSTHOOK: SUBMITTING CONTEXT UPDATE ---');
            System.debug(JSON.serializePretty(updateInput));
            industriesContext.updateContextAttributes(updateInput);
        }

        RevSignaling.TransactionResponse response = new RevSignaling.TransactionResponse();
        response.status = RevSignaling.TransactionStatus.SUCCESS;
        return response;
    }
}
```

#### See Also

- [Pricing Procedure Plans for B2B Stores](https://help.salesforce.com/s/articleView?id=commerce.comm_pricing_procedure_plans.htm&release=264.0.0&type=5 "Use procedure plans to customize how prices are calculated in your B2B store. A procedure plan is a framework that combines your pricing procedure with optional custom code (Apex hooks). The procedure plan defines the sequence that your custom logic and pricing procedure follow to calculate the final price.")
- [Configure Pricing Procedure Plans for B2B Stores](https://help.salesforce.com/s/articleView?id=commerce.comm_configure_pricing_procedure_plans.htm&release=264.0.0&type=5 "Configure pricing procedure plans to customize how pricing is calculated in your B2B stores. Create a procedure plan from the default Commerce procedure plan template and then customize it with your own context definitions, pricing procedures, and Apex hooks.")