IProductOrderPOST Apex Interface
The IProductOrderPOST Apex interface enables customization of POST operations using Apex lifecycle hooks. One handler routes into four mutually exclusive sub-flows from productOrderItem[].action (and product.id). Implementations can validate and enrich creation, amendment, renewal, or cancellation payloads, and run side effects after the pipeline, while the client-visible order is re-fetched via GET.
This interface supports the following hooks.
- transformRequest
- configureDefaultValidations
- applyCustomValidations
- preServiceHook
- handlePostOperation
Note
These hooks are not used for Product Order POST. If implemented, the methods are required to compile, but they are never invoked (constructGraphQL / constructMutationGraphQL return null).
customiseGraphQLQuerycustomiseMutationPayload
Rewrite the outbound payload in preServiceHook via modifiedPayload.
Mixed actions (add+renew, cancel+anything, and similar) are rejected by validateSingleActionType before any Apex hook.
| Sub-flow | How it is detected | Pipeline steps |
|---|---|---|
| Standard create/submit | Default when no amend/renew/cancel action | orderIngestion → activateOrder → submitOrder (last two skipped if the org has Quotes Tax) |
| Amendment | Any item action=amend, or a non-cancel/non-renew item with existing product.id | initiateAmendment → activateOrder → submitOrder |
| Renewal | Any item action=renew (checked first) | initiateRenewal → activateOrder → submitOrder |
| Cancellation | Any item action=cancel | initiateCancellation → activateOrder → submitOrder |
preServiceHook fires up to three times per request (once per step) using finalPayload re-invocation: build payload → Apex → execute with modifiedPayload. Switch on serviceName.
The following table lists common use cases for the POST operation.
| Use Case | Hook(s) | Description | Example Scenarios | Benefit |
|---|---|---|---|---|
| Force a sub-flow | transformRequest | Runs before sub-flow detection. Mutating productOrderItem[].action (or injecting product.id) changes which pipeline is built. | • Set action=amend when product.id is present• Route cancel vs renew vs amend to a single action | Avoid mixed-action rejection and pick the correct pipeline |
| Enforce create rules | applyCustomValidations | Only loud HTTP error path. Return top-level validationStatus / validationMessage. Nested result is silently dropped. | • Require requestedStartDate on add items• Duplicate amendment/renewal checks the handler does not run | Prevents invalid orders before service calls |
| Inject ingestion preferences | preServiceHook (orderIngestion) | Add scalar keys on the initiating payload. orderGraph is a live Java object — prefer scalars. | • pricingPref, taxPref | Channel-specific pricing/tax without forking the handler |
| Override amendment quantity deltas | preServiceHook (initiateAmendment) | Mutate quantityChanges before execute. | • Org rounding rules | Keep quantity math consistent |
| Hold activated but unsubmitted | preServiceHook (submitOrder) | skipCall: true is not an HTTP error. Skipping an initiating step leaves a null order id. Skipping submitOrder is the only coherent partial skip. | • Nightly batch submit | Partial pipeline without a broken order |
| Audit after POST | handlePostOperation | Return is discarded. Client body is GET re-fetch. A non-empty map fires telemetry (up to 3× from preServiceHook). | • Platform events, correlation ids | Side effects without shaping the HTTP body |
This hook transforms the request context before processing any operation. It is invoked before sub-flow detection.
Map<String, Object> transformRequest(Map<String, Object> context)
Default spec (all true): orderLevelAttributesValidation, relatedPartyValidation, productOrderItemsValidation. Return a map toggling those flags.
Handler validateAmendmentItems / validateRenewalItems / validateCancellationItems do not run in the normal request flow (isXRequest is still false during validation). Duplicate critical checks in applyCustomValidations. Missing product.id is enforced later in payload builders as HTTP 400.
Map<String, Boolean> configureDefaultValidations(Map<String, Boolean> defaultValidationConfiguration, Map<String, Object> context)
This hook validates custom business logic before the service-call pipeline. If validation fails, it rejects the request and returns an error response.
The handler processes return values as follows.
Success
- Return a map with
validationStatusset to any value other than"fail"(case-insensitive), or an empty map. - The API request continues normally.
Failure
- Return a top-level map with
validationStatusset to"fail". Nested"result"is silently dropped. - The API request is terminated immediately (
ValidationException). Default mapping is HTTP 422 unlessvalidationDetailsoverrides.
Map<String, Object> applyCustomValidations(Map<String, Object> context)
This hook runs once per pipeline step (up to three times). Return {skipCall, modifiedPayload}. Exceptions are swallowed per step (WARNING); the original payload is used. skipCall is not an HTTP error.
| serviceName | Payload shape | Notes |
|---|---|---|
| orderIngestion | {orderGraph} | Standard create. Prefer scalar keys such as pricingPref |
| initiateAmendment | {amendAssetIds, amendStartDate, amendOutputType, itemQuantities, quantityChanges} | Amendment initiating step |
| initiateRenewal | {renewAssetIds, renewStartDate, renewOutputType, renewEndDate?} | Renewal initiating step |
| initiateCancellation | {cancelAssetIds, cancelStartDate, cancelOutputType} | Cancellation initiating step |
| activateOrder | {orderId, orderNumber} | Shared. Skipped on standard flow when org has Quotes Tax |
| submitOrder | {orderId, orderNumber} | Shared. skipCall here is the only coherent partial skip |
Map<String, Object> preServiceHook(String serviceName, Map<String, Object> payload, Map<String, Object> executionContext, Map<String, Object> metadata)
graphQLQueryResultAsMap is synthetic {"data": flowResult} from orderIngestionResult / initiateAmendmentResult / initiateRenewalResult / initiateCancellationResult. constructedTMFResponse is the request body. Return is discarded. The client representation is GET retrieveResource.
Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)