IProductOrderDELETE Apex Interface

The IProductOrderDELETE Apex interface provides extensibility for DELETE operations through Apex-based validations, mutation-payload adjustments, and post-operation processing. The handler does not issue DELETE DML against Order. It performs a soft delete: a GraphQL OrderUpdate mutation whose Order input is intentionally empty. The Apex class must inject the deleted marker (for example Status = "Cancelled") via customiseMutationPayload. Without that injection, the mutation is structurally valid but semantically empty — nothing about the order visibly changes.

This interface supports the following hooks.

  • transformRequest
  • applyCustomValidations
  • customiseMutationPayload
  • handlePostOperation

These hooks are not used for Product Order DELETE.

  • configureDefaultValidationsgetDefaultValidationSpec() returns {}. Apex is not called when the spec is empty.
  • customiseGraphQLQuery — required to compile; never invoked.

Note

Availability. DELETE is interface-mandatory. If no Apex class implements IProductOrderDELETE, processRequest throws HTTP 400 before ID resolution, validation, or GraphQL. Exactly one implementor per org.

{id} is treated as an OrderNumber and resolved to Order.Id. After transformRequest, the handler honors any Apex-written id, then unconditionally re-resolves it as an OrderNumber. Write an OrderNumber into context["id"], not a Salesforce ID.

The HTTP response body is always null. handlePostOperation return is discarded.

Deletion Lifecycle Use Cases and Hook Mapping 

The following table lists common use cases for the DELETE operation.

Use CaseHook(s)DescriptionExample ScenariosBenefit
Inject cancelled Status and audit fieldscustomiseMutationPayloadThis is the only hook that makes DELETE real. Target alias softDeleteProductOrder. Use addFields on the empty OrderUpdate.Status = Cancelled
Deleted_By__c, Deletion_Timestamp__c
Turns the empty mutation into a real soft-delete
Veto Activated, cascade, or permissionapplyCustomValidationsFail before mutation. Return top-level validationStatus / validationMessage / validationDetails. Nested result is ignored. Default HTTP 422; details can override (404, 403, 409).• Block Activated / InProgress
• Block if active OrderItems exist
• Custom permission Can_Delete_Product_Order
Database is never touched on fail
Stamp a correlation idtransformRequestExtra keys on the shared context are visible to later hooks and platform events.auditTraceIdTraceability across cascade cancel and events
Seed an OrderNumber aliastransformRequestWrite OrderNumber into context["id"] before the handler lookup.• Partner order numberCorrect OrderNumber re-resolution
Cascade-cancel items and publish an eventhandlePostOperationRuns even on GraphQL errors. constructedTMFResponse is deleteResult (success, deletedId, errors). Check success before DML.• Cancel OrderItems
• Publish OrderDeleted__e
Side effects after a successful mutation

transformRequest 

This hook transforms the request context before processing any operation. It is invoked early because an implementor is guaranteed on every DELETE that reaches GraphQL.

Hook Method 

Map<String, Object> transformRequest(Map<String, Object> context)

Sample Apex Implementation 

1global static Map<String, Object> transformRequest(Map<String, Object> context) {
2    context.put('auditTraceId', new Uuid().toString());
3    return context;
4}

applyCustomValidations 

This hook validates custom business logic before constructing the mutation. If validation fails, it rejects the request and returns an error response. The database is never touched on fail. After this hook, context["id"] is already the resolved Salesforce Order Id.

The handler processes return values as follows.

Success

  • Return a map with validationStatus set to any value other than "fail" (case-insensitive), or an empty map.

Failure

  • Return a top-level map with validationStatus set to "fail".
  • Nested "result" is silently ignored.
  • Default HTTP 422 UNPROCESSABLE_REQUEST; validationDetails.statusCode / errorCode can override.
  • Thrown Apex exceptions become generic HTTP 400.

Hook Method 

Map<String, Object> applyCustomValidations(Map<String, Object> context)

Sample Apex Implementation 

1global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
2    String orderId = (String) context.get('id');
3    Order ord = [SELECT Id, Status FROM Order WHERE Id = :orderId LIMIT 1];
4
5    if (ord.Status == 'Activated' || ord.Status == 'InProgress') {
6        return failValidation('Cannot delete an order in status ' + ord.Status, 400, 'BAD_REQUEST');
7    }
8    Integer dependentItems = [
9        SELECT COUNT() FROM OrderItem WHERE OrderId = :orderId AND Status != 'Cancelled'
10    ];
11    if (dependentItems > 0) {
12        return failValidation('Cannot delete order with ' + dependentItems + ' active order items', 409, 'CONFLICT');
13    }
14    if (!FeatureManagement.checkPermission('Can_Delete_Product_Order')) {
15        return failValidation('Insufficient permissions to delete this ProductOrder', 403, 'FORBIDDEN');
16    }
17    return new Map<String, Object>();
18}
19
20private static Map<String, Object> failValidation(String message, Integer statusCode, String errorCode) {
21    return new Map<String, Object>{
22        'validationStatus'  => 'fail',
23        'validationMessage' => message,
24        'validationDetails' => new Map<String, Object>{
25            'statusCode' => statusCode,
26            'errorCode'  => errorCode
27        }
28    };
29}

customiseMutationPayload 

This hook modifies the mutation payload before the GraphQL OrderUpdate is executed. The base mutation is Order{}. Return {"nodes":[{path, addFields, modifyInput, insertGraphQlSnippet, updateGraphQlSnippet}]}. Target alias softDeleteProductOrder.

mutationGraphQLPayload is {"query": "<mutation string>"}.

Hook Method 

Map<String, Object> customiseMutationPayload(Map<String, Object> mutationGraphQLPayload, Map<String, Object> context)

Sample Apex Implementation 

1global static Map<String, Object> customiseMutationPayload(
2        Map<String, Object> mutationGraphQLPayload, Map<String, Object> context) {
3    return new Map<String, Object>{
4        'nodes' => new List<Object>{
5            new Map<String, Object>{
6                'path' => 'softDeleteProductOrder',
7                'addFields' => new Map<String, Object>{
8                    'Status' => 'Cancelled',
9                    'Deletion_Reason__c' => 'Customer requested cancellation',
10                    'Deleted_By__c' => UserInfo.getUserId(),
11                    'Deletion_Timestamp__c' => System.now()
12                }
13            }
14        }
15    };
16}

handlePostOperation 

This hook runs after the mutation, even if GraphQL returned errors. Use it only for side effects. Do not try to shape the HTTP body — it is always null.

Hook Method 

Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)

Sample Apex Implementation 

1global static Map<String, Object> handlePostOperation(
2        Map<String, Object> graphQLQueryResultAsMap,
3        Map<String, Object> constructedTMFResponse,
4        Map<String, Object> context) {
5    Boolean success = (Boolean) constructedTMFResponse.get('success');
6    String deletedId = (String) constructedTMFResponse.get('deletedId');
7    if (success == true && deletedId != null) {
8        List<OrderItem> items = [SELECT Id, Status FROM OrderItem WHERE OrderId = :deletedId];
9        for (OrderItem oi : items) {
10            oi.Status = 'Cancelled';
11        }
12        update items;
13        EventBus.publish(new OrderDeleted__e(
14            OrderId__c = deletedId,
15            TraceId__c = (String) context.get('auditTraceId')));
16    }
17    return new Map<String, Object>(); // discarded
18}

Full Implementation Example 

Implement all six methods. Combine correlation id, state/cascade/permission veto, Status injection, and cascade-cancel on success. Return an empty map from customiseGraphQLQuery.

1global class ProductOrderDELETEExtension implements comms_apex_ext.IProductOrderDELETE {
2
3    global static Map<String, Object> transformRequest(Map<String, Object> context) {
4        context.put('auditTraceId', new Uuid().toString());
5        return context;
6    }
7
8    global static Map<String, Boolean> configureDefaultValidations(
9            Map<String, Boolean> defaultValidationConfiguration,
10            Map<String, Object> context) {
11        return defaultValidationConfiguration; // inert
12    }
13
14    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
15        return new Map<String, Object>();
16    }
17
18    global static Map<String, Object> customiseGraphQLQuery(
19            Map<String, Object> graphQLAsMap, Map<String, Object> context) {
20        return new Map<String, Object>(); // never invoked
21    }
22
23    global static Map<String, Object> customiseMutationPayload(
24            Map<String, Object> mutationGraphQLPayload, Map<String, Object> context) {
25        return new Map<String, Object>{
26            'nodes' => new List<Object>{
27                new Map<String, Object>{
28                    'path' => 'softDeleteProductOrder',
29                    'addFields' => new Map<String, Object>{ 'Status' => 'Cancelled' }
30                }
31            }
32        };
33    }
34
35    global static Map<String, Object> handlePostOperation(
36            Map<String, Object> graphQLQueryResultAsMap,
37            Map<String, Object> constructedTMFResponse,
38            Map<String, Object> context) {
39        return new Map<String, Object>();
40    }
41}

GraphQL Mutation — Delete Operations 

Delete for Product Order is a soft OrderUpdate with an empty Order input. The primary extension mechanism is addFields (or modifyInput) on alias softDeleteProductOrder.

Transformation Instructions (JSON):

1{
2  "nodes": [{
3    "path": "softDeleteProductOrder",
4    "addFields": {
5      "Status": "Cancelled"
6    }
7  }]
8}

Without this node, the mutation succeeds structurally and the Order is unchanged.

See Also