---
schema_version: "1"
id: 57d5db3e23d6edb3ee27ad6e4302a2d9efce21d4ce712903e7880f89edd58eaa
title: ICustomerManagementPATCH Apex Interface
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/industries-integrations/comms/guides/Extensibility/ICustomerManagementPATCH.md
canonical_url: https://developer.salesforce.com/docs/industries/communications/guide/ICustomerManagementPATCH.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: a02ad4f8b0c42ba116bf50f508f184b3330d1fc79839b3ded494b08c15be7c70
products:
  - Communications Cloud TM Forum API
  - Industries Integrations
---
# ICustomerManagementPATCH Apex Interface

The ICustomerManagementPATCH interface supports extensibility for PATCH operations by allowing validation, transformation, and post-processing of update requests. Implementations can enforce business rules, adjust mutation payloads, and refine the resulting responses maintaining TMF-compliant behavior.

This interface supports the following hooks.

- [transformRequest](#transformrequest)
- [applyCustomValidations](#applycustomvalidations)
- [customiseMutationPayload](#customisemutationpayload)
- [handlePostOperation](#handlepostoperation)

**note:** These hook are not used for the Customer Management API. If implemented, the method may be invoked, but its return values are ignored.

- `configureDefaultValidations`
- `customiseGraphQLQuery`

## Update Lifecycle Use Cases and Hook Mapping

The following table lists common use cases and hooks required for PATCH operation.

| **Use Case**                   | **Hook(s)**                | **Description** <div style="width:290px"></div>                                                                                                                                                                   | **Example Scenarios** <div style="width:290px"></div>                                                                                                                                                                                 | **Benefit** <div style="width:290px"></div>                                                        |
| ------------------------------ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Enforce Status Transitions** | `applyCustomValidations`   | Validates requested status changes before updating the record to ensure transitions adhere to defined lifecycle rules, regulatory constraints, or business policies. Stops invalid or unauthorized state changes. | • Prevent transition from _Inactive_ → _Active_ without required approvals• Block changes to _Suspended_ unless preconditions are met• Enforce linear lifecycle progression (e.g., _Pending_ → _Active_, but not _Closed_ → _Active_) | Prevents invalid or noncompliant state transitions and preserves data integrity                    |
| **Maintain Audit Trail**       | `customiseMutationPayload` | Automatically enriches the update payload with audit metadata before persistence. Ensures all modifications are traceable and compliant with auditing standards.                                                  | • Add “modifiedBy” and timestamp fields• Track system-initiated vs. user-initiated changes• Add audit IDs for downstream monitoring systems                                                                                           | Provides a complete and reliable audit trail for internal governance and external compliance needs |
| **Validate Data Format**       | `applyCustomValidations`   | Ensures updated data—such as contact details, identifiers, or structured fields—meets required formatting rules before the update is processed. Prevents malformed or inconsistent data from entering the system. | • Validate phone number format• Confirm email structure and domain correctness• Ensure address fields follow standardized formats                                                                                                     | Improves data quality, prevents inconsistencies, and enhances downstream system reliability        |

## transformRequest

This hook transforms the request context before processing any operation. It is invoked early in the request lifecycle, allowing implementers to adjust the context for subsequent extensibility hooks.

## Hook Method

`Map<String, Object> transformRequest(Map<String, Object> context)`

## Sample Apex Implementation

```
public Map<String, Object> transformRequest(Map<String, Object> context) {
       if (context == null) {
           return context;
       }

       Object idObj = context.get('id');
       if (idObj == null || !(idObj instanceof String)) {
           return context;
       }

       String idValue = (String)idObj;

       // Check if the ID looks like a Salesforce ID (starts with 001, 003, etc.)
       // If not, assume it's an AccountNumber and resolve it
       if (idValue.length() == 15 || idValue.length() == 18) {
           // Looks like a Salesforce ID, return context as-is
           return context;
       }

       // Query Account objects where AccountNumber matches the provided ID
       List<Account> accounts = [
           SELECT Id, AccountNumber
           FROM Account
           WHERE AccountNumber = :idValue
           LIMIT 1
       ];

       // If found, replace the ID in context with the Salesforce ID
       if (!accounts.isEmpty() && accounts[0].AccountNumber != null) {
           context.put('id', accounts[0].Id);
       }

       return context;
   }
```

## applyCustomValidations

This hook validates custom business logic before updating a customer data. If validation fails, rejects the request and returns an error response.

The handler processes return values as follows.

Success:

- Return a map with validationStatus set to any value other than "fail" (case-insensitive).
- The API request continues normally.
- Other fields in the map are logged but not used.

Failure:

- Return a map with validationStatus set to "fail" (case-insensitive).
- The API request is terminated immediately.
- Raise a ValidationException containing
  - message: value from validationMessage key (or "Validation failed" by default)
  - details: value from validationDetails key (optional)
- The client receives an HTTP 400 error response.

## Hook Method

`Map<String, Object> applyCustomValidations(Map<String, Object> context)`

## Sample Apex Implementation

```
public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
       // Input context:
       // {
       //   api: 'CustomerManagement',
       //   version: '4.0',
       //   customerId: '001xx000003DHP',
       //   requestBody: { name: 'Updated Name', status: 'Inactive' }
       // }

       String customerId = (String) context.get('id');
       Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
       Map<String, Object> validationResult = new Map<String, Object>();

       try {
           String newStatus = (String) requestBody.get('status');

           // Validate status transition
           if (newStatus != null) {
               Account currentAccount = [SELECT Id, Status__c FROM Account WHERE Id = :customerId LIMIT 1];

               if (!isValidStatusTransition(currentAccount.Status__c, newStatus)) {
                   validationResult.put('validationStatus', 'FAIL');
                   validationResult.put('validationMessage', 'Invalid status transition from ' + currentAccount.Status__c + ' to ' + newStatus);
                   validationResult.put('validationDetails', new Map<String, Object>{
                       'currentStatus' => currentAccount.Status__c,
                       'requestedStatus' => newStatus,
                       'allowedTransitions' => getAllowedTransitions(currentAccount.Status__c)
                   });
                   return validationResult;
               }
           }

           // Validate contact information if provided
           Map<String, Object> contactMedium = (Map<String, Object>) requestBody.get('contactMedium');
           if (contactMedium != null && !isValidContactMedium(contactMedium)) {
               validationResult.put('validationStatus', 'FAIL');
               validationResult.put('validationMessage', 'Invalid contact medium format');
               return validationResult;
           }

           // PASS: All validations passed
           validationResult.put('validationStatus', 'PASS');
           validationResult.put('validationMessage', 'Customer update validation passed');

       } catch (Exception e) {
           // FAIL: Error during validation
           validationResult.put('validationStatus', 'FAIL');
           validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
       }

       return validationResult;
   }

   private Boolean isValidStatusTransition(String currentStatus, String newStatus) {
       // Define valid status transitions
       Map<String, Set<String>> validTransitions = new Map<String, Set<String>>{
           'Active' => new Set<String>{'Inactive', 'Suspended'},
           'Inactive' => new Set<String>{'Active'},
           'Suspended' => new Set<String>{'Active', 'Inactive'}
       };

       Set<String> allowed = validTransitions.get(currentStatus);
       return allowed != null && allowed.contains(newStatus);
   }

   private List<String> getAllowedTransitions(String currentStatus) {
       Map<String, List<String>> transitions = new Map<String, List<String>>{
           'Active' => new List<String>{'Inactive', 'Suspended'},
           'Inactive' => new List<String>{'Active'},
           'Suspended' => new List<String>{'Active', 'Inactive'}
       };

       return transitions.get(currentStatus);
   }

   private Boolean isValidContactMedium(Map<String, Object> contactMedium) {
       String mediumType = (String) contactMedium.get('mediumType');
       return mediumType != null && !mediumType.isBlank();
   }

```

## customiseMutationPayload

This hook modifies the mutation payload before updating a customer record. Use this to add computed fields, transform values, or apply business logic transformations.

## Hook Method

`Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context)`

## Sample Apex Implementation

```
/**
     * Customizes the mutation payload before the GraphQL mutation is executed.
     * Reads Description and Industry from the request body in context and adds them
     * to the Account node at alias path "customerAccount".
     *
     * @param request The mutation request payload
     * @param context Request context containing API details and request body
     * @return Map containing mutation transformation instructions with nodes array:
     *         - nodes[0]: Contains path='customerAccount' and addFields with Description and Industry
     */
    public Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context) {
        // Return mutation transformation instructions: add Account fields at alias path "customerAccount"
        Map<String, Object> nodes0 = new Map<String, Object>();
        nodes0.put('path', 'customerAccount');
        Map<String, Object> addFields = new Map<String, Object>();

        // Read Description and Industry from request body in context
        Map<String, Object> requestBody = (Map<String, Object>)context.get('requestBody');
        if (requestBody != null) {
            String description = (String)requestBody.get('description');
            String industry = (String)requestBody.get('industry');

            // Add Description to Account if present in request
            if (description != null) {
                addFields.put('Description', description);
            }
            // Add Industry to Account if present in request
            if (industry != null) {
                addFields.put('Industry', industry);
            }
        }

        nodes0.put('addFields', addFields);

        // Build nodes array containing the transformation instructions
        List<Object> nodes = new List<Object>();
        nodes.add(nodes0);

        // Return result with nodes array
        Map<String, Object> result = new Map<String, Object>();
        result.put('nodes', nodes);
        return result;
    }


```

## handlePostOperation

This hook post-processes and transforms the API response after data is updated.

## Hook Method

`Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)`

## Full Implementation Example

Here's a complete sample Apex implementation using all supported hooks.

```
/**
 * Complete Customer Management POST API Extension
 * Demonstrates all three applicable lifecycle hooks with business logic
 */
public class CustomerManagementPATCHExtension implements comms_apex_ext.ICustomerManagementPATCH {

    /**
     * Hook 1: Apply custom validations
     */
    public Map<String, Object> applyCustomValidations(Map<String, Object> context) {
        Map<String, Object> requestBody = (Map<String, Object>) context.get('requestBody');
        Map<String, Object> validationResult = new Map<String, Object>();

        try {
            String customerName = (String) requestBody.get('name');
            String customerType = (String) requestBody.get('type');

            // Validate required fields
            if (String.isBlank(customerName)) {
                validationResult.put('validationStatus', 'FAIL');
                validationResult.put('validationMessage', 'Customer name is required');
                return validationResult;
            }

            // Validate customer type
            if (!isValidCustomerType(customerType)) {
                validationResult.put('validationStatus', 'FAIL');
                validationResult.put('validationMessage', 'Invalid customer type');
                return validationResult;
            }

            // Check for duplicates
            List<Account> existingAccounts = [SELECT Id FROM Account WHERE Name = :customerName LIMIT 1];
            if (!existingAccounts.isEmpty()) {
                validationResult.put('validationStatus', 'FAIL');
                validationResult.put('validationMessage', 'Customer with this name already exists');
                return validationResult;
            }

            validationResult.put('validationStatus', 'PASS');
            validationResult.put('validationMessage', 'Validation passed');

        } catch (Exception e) {
            validationResult.put('validationStatus', 'FAIL');
            validationResult.put('validationMessage', 'Validation error: ' + e.getMessage());
        }

        return validationResult;
    }

    /**
     * Hook 2: Customize mutation payload
     */
    /**
     * Customizes the mutation payload before the GraphQL mutation is executed.
     * Reads Description and Industry from the request body in context and adds them
     * to the Account node at alias path "customerAccount".
     *
     * @param request The mutation request payload
     * @param context Request context containing API details and request body
     * @return Map containing mutation transformation instructions with nodes array:
     *         - nodes[0]: Contains path='customerAccount' and addFields with Description and Industry
     */
    public Map<String, Object> customiseMutationPayload(Map<String, Object> request, Map<String, Object> context) {
        // Return mutation transformation instructions: add Account fields at alias path "customerAccount"
        Map<String, Object> nodes0 = new Map<String, Object>();
        nodes0.put('path', 'customerAccount');
        Map<String, Object> addFields = new Map<String, Object>();

        // Read Description and Industry from request body in context
        Map<String, Object> requestBody = (Map<String, Object>)context.get('requestBody');
        if (requestBody != null) {
            String description = (String)requestBody.get('description');
            String industry = (String)requestBody.get('industry');

            // Add Description to Account if present in request
            if (description != null) {
                addFields.put('Description', description);
            }
            // Add Industry to Account if present in request
            if (industry != null) {
                addFields.put('Industry', industry);
            }
        }

        nodes0.put('addFields', addFields);

        // Build nodes array containing the transformation instructions
        List<Object> nodes = new List<Object>();
        nodes.add(nodes0);

        // Return result with nodes array
        Map<String, Object> result = new Map<String, Object>();
        result.put('nodes', nodes);
        return result;
    }

    /**
     * Hook 3: Post-process response
     */
    public Map<String, Object> handlePostOperation(
        Map<String, Object> graphQLQueryResultAsMap,
        Map<String, Object> constructedTMFResponse,
        Map<String, Object> context
    ) {
        return null;
    }

    /**
     * NOT APPLICABLE: configureDefaultValidations is not used for Customer Management API
     */
    public Map<String, Boolean> configureDefaultValidations(
        Map<String, Boolean> defaultValidationConfiguration,
        Map<String, Object> context
    ) {
        return null;
    }

    private Boolean isValidCustomerType(String type) {
        Set<String> validTypes = new Set<String>{'Customer', 'Prospect', 'Partner'};
        return validTypes.contains(type);
    }
    private String determineCustomerSegment(String name) {
        if (name.length() > 20) {
            return 'Enterprise';
        } else if (name.length() > 10) {
            return 'Mid-Market';
        } else {
            return 'SMB';
        }
    }
    **
     * NOT APPLICABLE: customiseGraphQLQuery is not used for Customer Management API
     */
    public Map<String, Object> customiseGraphQLQuery(
        Map<String, Object> graphQLAsMap,
        Map<String, Object> context
    ) {
       return null;
    }
}

```

## GraphQL Mutation - Update Operations

Update mutations modify existing records and return the updated records with the specified output fields.

### Update Enhancements: Add Output Fields

**Original Mutation (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: { Name: "Updated Name" }
    }) {
      Record {
        Id
        Name { value }
      }
    }
  }
}
```

**Transformation Instructions (JSON):**

```
{
  "nodes": [{
    "path": "uiapi.customerAccount",
    "addFields": {
      "LastModifiedDate": "2024-01-15",
      "ModifiedBy": "user123",
      "Status": "Updated"
    }
  }]
}
```

**Result (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: { Name: "Updated Name" }
    }) {
      Record {
        Id
        Name { value }
        LastModifiedDate
        ModifiedBy
        Status
      }
    }
  }
}
```

### Update Enhancements: Modify Input – Add Fields

**Original Mutation (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: { Name: "Updated Name" }
    }) {
      Record {
        Id
        Name { value }
      }
    }
  }
}
```

**Transformation Instructions (JSON - Add Input Fields):**

```
{
  "nodes": [{
    "path": "uiapi.customerAccount",
    "modifyInput": {
      "addInputFields": {
        "Industry": "Technology",
        "Status": "Active"
      }
    }
  }]
}
```

**Result (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: {
        Name: "Updated Name",
        Industry: "Technology",
        Status: "Active"
      }
    }) {
      Record {
        Id
        Name { value }
      }
    }
  }
}
```

### Update Enhancements: Modify Input and Add Output Fields

**Transformation Instructions (JSON):**

```
{
  "nodes": [{
    "path": "uiapi.customerAccount",
    "addFields": {
      "LastModifiedDate": "2024-01-15",
      "ChangeLog": "Updated via API"
    },
    "modifyInput": {
      "addInputFields": {
        "UpdateReason": "Bulk Update",
        "UpdateSource": "API"
      },
      "inputModifications": {
        "Status": "Active"
      }
    }
  }]
}
```

**Result (GraphQL):**

```
mutation UpdateCustomerAndRelatedEntities {
  uiapi {
    customerAccount: AccountUpdate(input: {
      Id: "001xx000003DHP"
      Account: {
        Name: "Updated Name",
        Status: "Active",
        UpdateReason: "Bulk Update",
        UpdateSource: "API"
      }
    }) {
      Record {
        Id
        Name { value }
        LastModifiedDate
        ChangeLog
      }
    }
  }
}
```
