Salesforce Order Management (OMS) is frequently underestimated in scoping. Clients see it as “just order tracking” until they hit the first edge case - partial fulfilment from multiple DCs, a split-ship with different carrier accounts, or a return initiated before delivery is complete. Getting the data model and orchestration right from day one saves months of rework.
The Order Lifecycle Model
Salesforce OMS uses a status-based lifecycle model for both the order summary and its child objects. Understanding the hierarchy is essential before you write a single line of Apex.
OrderSummary (OrderLifeCycleType = MANAGED)
├── OrderItemSummary (per SKU/line)
│ ├── FulfillmentOrder (per ship-from location)
│ │ ├── FulfillmentOrderLineItem
│ │ └── FulfillmentOrderItemAdjustment
│ └── OrderItemSummaryChange (for amendments)
├── OrderPaymentSummary
└── CreditMemo (for refunds)
The OrderLifeCycleType field on OrderSummary controls whether the platform manages status transitions (MANAGED) or your code does (UNMANAGED). Always use MANAGED unless you have a very specific reason not to - the platform handles a lot of state validation for you.
Fulfilment Order Routing
The routing step - deciding which DC fulfils which items - is where most of the complexity lives. OMS provides a routing framework but the routing logic itself is yours to implement.
Simple routing (single DC, no inventory check) can use the built-in RoutingService invocable action directly from a Flow:
Order Created → Route Order → Create Fulfilment Orders → Assign to Carrier
Complex routing requires an Apex-based routing implementation. The contract is the RoutingService interface: you receive a list of OrderSummary IDs and must return a list of FulfillmentOrderLineItemInput records mapped to specific inventory locations.
My standard implementation:
- Query inventory availability across all candidate locations via an external API or Inventory Management object
- Score each location (distance to customer, stock depth, carrier SLA availability)
- Apply split-ship logic if no single location can fulfil the complete order
- Return allocations - the platform creates the
FulfillmentOrderrecords
public class OrderRoutingService implements RoutingServiceImpl {
public List<ConnectApi.FulfillmentOrderLineItemInput> routeOrder(
List<Id> orderSummaryIds
) {
List<ConnectApi.FulfillmentOrderLineItemInput> allocations = new List<>();
for (Id osId : orderSummaryIds) {
List<OrderItemSummary> lines = queryOrderLines(osId);
Map<String, Integer> inventoryByLocation = checkInventory(lines);
allocations.addAll(allocateLines(lines, inventoryByLocation));
}
return allocations;
}
}
Split Fulfilment Handling
Split ships are the most common source of post-go-live defects. When an order splits across two DCs, you end up with:
- Two
FulfillmentOrderrecords, each with their own carrier booking - Two tracking numbers to present to the customer
- Two independent shipment confirmations
- Partial capture against the
OrderPaymentSummary
The payment partial capture piece trips people up. You must capture against each FulfillmentOrder shipment, not the OrderSummary total. Use the EnsureFundsOrderSummaryAsync action and handle the SOFT_DECLINE and HARD_DECLINE outcomes explicitly - don’t assume payment succeeds.
ConnectApi.EnsureFundsAsyncInputRepresentation input =
new ConnectApi.EnsureFundsAsyncInputRepresentation();
input.targetFulfillmentOrderId = fulfillmentOrderId;
ConnectApi.EnsureFundsAsyncOutputRepresentation result =
ConnectApi.OrderManagement.ensureFundsOrderSummaryAsync(orderSummaryId, input);
if (result.processesAsyncApiResponse.processResults[0].resultStatusCode == 'SOFT_DECLINE') {
// Trigger retry logic or alert ops team
}
Returns and Refunds
Returns in OMS are modelled as ReturnOrder → ReturnOrderLineItem → CreditMemo. The CreditMemo drives the actual payment refund.
Key design decision: returnless refunds. For low-value items, issue the CreditMemo immediately without waiting for physical return receipt. Implement a threshold in your Flow (e.g. item value < £20 → immediate refund; > £20 → wait for warehouse confirmation).
The refund flow:
Customer requests return (Experience Cloud / self-service)
→ Create ReturnOrder
→ Optional: Wait for warehouse scan (ReturnOrder status = Received)
→ Create ReturnOrderLineItem for each returned item
→ EnsureRefundsOrderSummary (platform handles CreditMemo creation)
→ Payment refund processed via OrderPaymentSummary
Carrier Integration Pattern
OMS doesn’t include carrier label generation - you need to integrate with your carrier (Sorted, EasyPost, Shipstation, direct carrier APIs). The standard pattern:
FulfillmentOrdermoves to statusFulfilment(picked and packed)- Platform Event fires (or trigger on FulfilmentOrder)
- Apex callout to carrier API → returns tracking number + label URL
- Update
FulfillmentOrderwith tracking info ShipOrderaction called → FulfillmentOrder status →Shipped- Customer notification fired from Flow
Keep carrier logic in a dedicated service class behind an interface so you can swap carriers or add carrier routing (e.g. DHL for international, Royal Mail for domestic) without touching the orchestration layer.
After-Sales Service in Service Cloud
The Commerce + OMS + Service Cloud integration is where multi-cloud implementations earn their complexity premium. The goal: a Service agent can see the full order lifecycle from within a Service Cloud case.
Use the standard OrderSummary relationship on the Case object. Surface the OrderSummaryId in the Case layout and use an Omnistudio FlexCard or standard related list to show fulfilment status, tracking, and return history.
For automated case creation on failed orders: a Platform Event from OMS → Process Builder/Flow → Case creation with the OrderSummaryId pre-populated.
Performance at Scale
OMS on large order volumes hits a few common bottlenecks:
Routing latency - synchronous routing during checkout will add latency visible to the customer. Route asynchronously via a Platform Event: checkout creates the OrderSummary, fires an event, a Queueable picks up routing. Show the customer “processing” while this happens.
Inventory check N+1 - don’t query inventory per order line in a loop. Batch inventory checks across all lines in a single API call.
FulfillmentOrder trigger depth - OMS creates multiple child records in sequence. Be careful with trigger logic on FulfillmentOrderLineItem - you can hit governor limits if your trigger logic is not bulkified.
Common Go-Live Risks
- Payment gateway timeout during EnsureFunds - implement async retry logic and an ops alerting Flow
- Carrier API downtime - build a fallback (queue the label request, retry after N minutes)
- Inventory oversell on split orders - reserve inventory atomically when routing, not when shipping
- Status sync lag with external WMS - design for eventual consistency; show the last-known status with a timestamp rather than assuming real-time accuracy