---
schema_version: "1"
id: 1e5224db1ea97824ed3108b798f2329171cd39cf0749ed498caff3ffb9fdda1a
title: IProductOrderDELETE Apex Interface
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/industries-integrations/comms/guides/Extensibility/TMF622/IProductOrderDELETE.md
canonical_url: https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderDELETE.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 3b7043004418162b2f8aef6e29c1504c6dd64dfeabfcf7b9cbb0d819b4bbcb29
products:
  - Communications Cloud TM Forum API
  - Industries Integrations
---
# 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

**note:** These hooks are not used for Product Order DELETE.

- `configureDefaultValidations` — `getDefaultValidationSpec()` returns `{}`. Apex is not called when the spec is empty.
- `customiseGraphQLQuery` — required to compile; **never invoked**.

**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 Case                                  | Hook(s)                    | Description                                                                                                                                                                                        | Example Scenarios                                                                                                      | Benefit                                          |
| ----------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Inject cancelled Status and audit fields  | `customiseMutationPayload` | **This is the only hook that makes DELETE real.** Target alias `softDeleteProductOrder`. Use `addFields` on the empty `OrderUpdate`.                                                               | • `Status = Cancelled`<br>• `Deleted_By__c`, `Deletion_Timestamp__c`                                                   | Turns the empty mutation into a real soft-delete |
| Veto Activated, cascade, or permission    | `applyCustomValidations`   | Fail **before** mutation. Return **top-level** `validationStatus` / `validationMessage` / `validationDetails`. Nested `result` is ignored. Default HTTP 422; details can override (404, 403, 409). | • Block Activated / InProgress<br>• Block if active OrderItems exist<br>• Custom permission `Can_Delete_Product_Order` | Database is never touched on fail                |
| Stamp a correlation id                    | `transformRequest`         | Extra keys on the shared `context` are visible to later hooks and platform events.                                                                                                                 | • `auditTraceId`                                                                                                       | Traceability across cascade cancel and events    |
| Seed an OrderNumber alias                 | `transformRequest`         | Write OrderNumber into `context["id"]` before the handler lookup.                                                                                                                                  | • Partner order number                                                                                                 | Correct OrderNumber re-resolution                |
| Cascade-cancel items and publish an event | `handlePostOperation`      | Runs even on GraphQL errors. `constructedTMFResponse` is `deleteResult` (`success`, `deletedId`, `errors`). Check `success` before DML.                                                            | • Cancel OrderItems<br>• 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

```
global static Map<String, Object> transformRequest(Map<String, Object> context) {
    context.put('auditTraceId', new Uuid().toString());
    return context;
}
```

## 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

```
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
    String orderId = (String) context.get('id');
    Order ord = [SELECT Id, Status FROM Order WHERE Id = :orderId LIMIT 1];

    if (ord.Status == 'Activated' || ord.Status == 'InProgress') {
        return failValidation('Cannot delete an order in status ' + ord.Status, 400, 'BAD_REQUEST');
    }
    Integer dependentItems = [
        SELECT COUNT() FROM OrderItem WHERE OrderId = :orderId AND Status != 'Cancelled'
    ];
    if (dependentItems > 0) {
        return failValidation('Cannot delete order with ' + dependentItems + ' active order items', 409, 'CONFLICT');
    }
    if (!FeatureManagement.checkPermission('Can_Delete_Product_Order')) {
        return failValidation('Insufficient permissions to delete this ProductOrder', 403, 'FORBIDDEN');
    }
    return new Map<String, Object>();
}

private static Map<String, Object> failValidation(String message, Integer statusCode, String errorCode) {
    return new Map<String, Object>{
        'validationStatus'  => 'fail',
        'validationMessage' => message,
        'validationDetails' => new Map<String, Object>{
            'statusCode' => statusCode,
            'errorCode'  => errorCode
        }
    };
}
```

## 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

```
global static Map<String, Object> customiseMutationPayload(
        Map<String, Object> mutationGraphQLPayload, Map<String, Object> context) {
    return new Map<String, Object>{
        'nodes' => new List<Object>{
            new Map<String, Object>{
                'path' => 'softDeleteProductOrder',
                'addFields' => new Map<String, Object>{
                    'Status' => 'Cancelled',
                    'Deletion_Reason__c' => 'Customer requested cancellation',
                    'Deleted_By__c' => UserInfo.getUserId(),
                    'Deletion_Timestamp__c' => System.now()
                }
            }
        }
    };
}
```

## 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

```
global static Map<String, Object> handlePostOperation(
        Map<String, Object> graphQLQueryResultAsMap,
        Map<String, Object> constructedTMFResponse,
        Map<String, Object> context) {
    Boolean success = (Boolean) constructedTMFResponse.get('success');
    String deletedId = (String) constructedTMFResponse.get('deletedId');
    if (success == true && deletedId != null) {
        List<OrderItem> items = [SELECT Id, Status FROM OrderItem WHERE OrderId = :deletedId];
        for (OrderItem oi : items) {
            oi.Status = 'Cancelled';
        }
        update items;
        EventBus.publish(new OrderDeleted__e(
            OrderId__c = deletedId,
            TraceId__c = (String) context.get('auditTraceId')));
    }
    return new Map<String, Object>(); // discarded
}
```

## 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`.

```
global class ProductOrderDELETEExtension implements comms_apex_ext.IProductOrderDELETE {

    global static Map<String, Object> transformRequest(Map<String, Object> context) {
        context.put('auditTraceId', new Uuid().toString());
        return context;
    }

    global static Map<String, Boolean> configureDefaultValidations(
            Map<String, Boolean> defaultValidationConfiguration,
            Map<String, Object> context) {
        return defaultValidationConfiguration; // inert
    }

    global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
        return new Map<String, Object>();
    }

    global static Map<String, Object> customiseGraphQLQuery(
            Map<String, Object> graphQLAsMap, Map<String, Object> context) {
        return new Map<String, Object>(); // never invoked
    }

    global static Map<String, Object> customiseMutationPayload(
            Map<String, Object> mutationGraphQLPayload, Map<String, Object> context) {
        return new Map<String, Object>{
            'nodes' => new List<Object>{
                new Map<String, Object>{
                    'path' => 'softDeleteProductOrder',
                    'addFields' => new Map<String, Object>{ 'Status' => 'Cancelled' }
                }
            }
        };
    }

    global static Map<String, Object> handlePostOperation(
            Map<String, Object> graphQLQueryResultAsMap,
            Map<String, Object> constructedTMFResponse,
            Map<String, Object> context) {
        return new Map<String, Object>();
    }
}
```

## 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):**

```
{
  "nodes": [{
    "path": "softDeleteProductOrder",
    "addFields": {
      "Status": "Cancelled"
    }
  }]
}
```

Without this node, the mutation succeeds structurally and the Order is unchanged.

## See Also

- [Extensibility for TMF622 v5 Product Ordering Management API](https://developer.salesforce.com/docs/industries/communications/guide/TMF622_extensibility.md)
- [IProductOrderGET Apex Interface](https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderGET.md)
- [IProductOrderPOST Apex Interface](https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderPOST.md)
- [IProductOrderPATCH Apex Interface](https://developer.salesforce.com/docs/industries/communications/guide/IProductOrderPATCH.md)
