B2C Order Flow¶
A direct-to-consumer order starts on the SFCC storefront and ends with a shipped package and captured payment. Along the way it touches NAV for inventory allocation, BatchStation/PrintStation if the order is customizable, Cirro for fulfillment, and SFCC again to settle payment. Every inter-system hop except two goes through Camel.
Flow¶
sequenceDiagram
actor Customer
participant SFCC
participant OMS
participant NAV
participant Camel
participant BS as BatchStation
participant PS as PrintStation
participant Cirro
Customer->>SFCC: Place order
OMS->>SFCC: Poll (Sidekiq cron)
SFCC-->>OMS: New orders
OMS->>NAV: Create sales order (SOAP)
Note over OMS: PENDING_NAV_RELEASE
Note over NAV: Allocate inventory
NAV->>Camel: ASB: nav-order-release
Camel->>OMS: PATCH status (via cm-osor-sys)
Note over OMS: RELEASED_FROM_NAV
alt Printable
OMS->>BS: ASB: int-prod-order-update-bs
BS->>PS: Print batch
PS-->>BS: Print complete
BS->>OMS: ASB: int-prod-batching-update-oms
Note over OMS: batch_reference set
end
OMS->>Camel: POST cm-ext-service-exp/fulfillment/request/cirro
Note over Camel: cm-ext-service-exp → ASB → cm-fulfill-prc (decrypt) → cm-cirro-sys
Camel->>Cirro: Create order (Bearer)
Note over OMS: WAITING_FULFILLMENT_CONFIRMATION
Cirro->>Camel: Acceptance webhook (cm-fulfill-exp)
Note over Camel: ASB: fulfillment-confirmation-cirro → cm-order-prc
Camel->>OMS: POST /order/cirro-confirmation (via cm-osor-sys)
Note over OMS: EXPORTED_TO_TPL
Note over Cirro: Order ships
Cirro->>Camel: Shipping webhook (tracking info)
Camel->>OMS: POST /osor_order/fulfill_orders
Note over OMS: SHIPPED
Note over OMS: Nav::ExportOrdersJob (cron)
OMS->>NAV: Shipment notice (SOAP)
OMS->>SFCC: POST /orderupdate (Shipped + Capture)
SFCC-->>Customer: Payment captured, order shipped
Step-by-Step¶
1. Order placement¶
Customer checks out on popsockets.com (SFCC). Payment is authorized but not yet captured.
2. OMS ingestion¶
OMS polls SFCC on a per-store Sidekiq cron (the recurring_jobs DB table — no static "every 5 minutes" config anywhere). Uses SCAPI or OCAPI depending on the sfcc_scapi_orders Flipper flag. The query is incremental, bounded by orders_last_updated on the demandware_clients row. Demandware::SyncOrdersBackupJob is the safety net for unexported orders.
3. NAV sales order¶
OMS creates a sales order in NAV via SOAP (NTLM auth, domain PSCORP). Job: Nav::CreateOrderJob → Nav::OrderExport.call. Status flips to PENDING_NAV_RELEASE. Inventory is not checked synchronously — NAV does allocation on its own clock.
4. NAV release¶
NAV publishes to ASB queue nav-order-release when allocation completes. It does not make an HTTP call. cm-order-prc consumes the queue, transforms the payload, and PUTs to cm-osor-sys at /order/nav-release, which forwards to OMS as PATCH /api/v3/osor_order/status/:order_id. The payload is also archived to Azure Blob. Handler on the OMS side: Api::V3::OsorController#update_status → Nav::ReleaseFromNavJob. Status becomes RELEASED_FROM_NAV.
5. Fork: printable vs finished goods¶
OMS checks order.active_printable_items?. The decision is OMS-side, not Camel.
Printable path:
- OMS publishes to ASB topic
int-prod-order-update-bs(viaPrintStation::CreateOrderService). - BatchStation consumes, creates a batch, dispatches to PrintStation.
- After printing, BatchStation publishes to
int-prod-batching-update-oms. OMS consumes and stores the order→batch mapping (batch_reference). - OMS now exports to Cirro.
Cirro::ExportFulfillmentRequestJobhas an explicit guard: printable orders with a blankbatch_referenceare skipped. Miss the batching step and the order silently never ships.
Finished-goods path: Skip batching. Go straight to step 6.
6. Cirro export¶
OMS POSTs to cm-ext-service-exp/fulfillment/request/cirro (Camel APIM endpoint, Basic Auth + Ocp-Apim-Subscription-Key). Job: Cirro::ExportFulfillmentRequestJob.
Inside Camel:
cm-ext-service-exppublishes to ASB queuefulfillment-request-amer(or-emea,-japanfor printable;-cirrofor finished goods).cm-fulfill-prcconsumes, AES-decrypts the payload.cm-fulfill-prcforwards tocm-cirro-sys, which adds the Bearer token and proxies the call to Cirro.cm-cirro-sysdoes not modify the payload.
OMS status: WAITING_FULFILLMENT_CONFIRMATION.
7. Two Cirro confirmations¶
This is the step most often confused. Cirro sends two separate notifications:
Acceptance (pre-shipping, Cirro has accepted the request):
- Cirro webhooks into
cm-fulfill-expat/fulfillment/confirmation/cirro. (Retiring — new endpoints go tocm-ext-service-exp. Check which is live in prod.) - Webhook publishes to ASB queue
fulfillment-confirmation-cirro. cm-order-prcconsumes (FulfillmentProcessServiceImpl) and POSTs tocm-osor-sysat/order/cirro-confirmation, which hits OMS.- OMS fires
FulfillmentRequestAccepted(orRejected). Status:EXPORTED_TO_TPL.
Shipping (Cirro has actually shipped the package):
- Cirro POSTs tracking info to OMS at
/api/v3/osor_order/fulfill_orders(via Camel). - OMS creates
OrderFulfillment+LineItemFulfillmentrecords. Status:SHIPPED.
8. NAV shipment notice¶
Not immediate. Nav::ExportOrdersJob runs on a recurring cron, finds shipped + paid orders with nav_exported=nil, and enqueues Nav::ExportOrderJob per order. Same NAV SOAP channel as step 3, but DOC_TYPE_SHIPMENT_NOTICE (6).
9. SFCC update + payment capture¶
One atomic SFCC call handles both. Demandware::FulfillPlatformOrder → POST {sfcc_base_url}/orderupdate with payload including payment_instructions: {execute: Capture}. SFCC marks the order shipped and captures payment in a single request.
Shopify orders (not covered here) use a separate CapturePaymentJob that hits Braintree/Shopify Payments independently.
Gotchas¶
- Two Cirro confirmations, not one. Acceptance goes via ASB; shipping-with-tracking goes via REST. We untangled a bug where
/order/cirro-confirmationwas being sent to the wrong endpoint. - Printable orders hard-require a
batch_referencebefore Cirro export. The guard is silent — you won't see a failure, the export just skips. - NAV release is queue-based, not an HTTP webhook. A common misconception.
nav-order-releaseASB queue →cm-order-prc→cm-osor-sys→ OMS. - Payment capture is embedded in SFCC's
/orderupdatecall. No separate API. Easy to miss when reading the code because thepayment_instructionsfield is the whole mechanism. - ASB queues carry AES-encrypted payloads when the sender is OMS. Camel-to-OMS REST calls are plaintext (APIM subscription key + Basic Auth).
- DLQ max-delivery is 1 on most queues. One failure → immediate dead-letter, no retries. Retry/DLQ was added for EDI routes (CI-303) but not the B2C path.
- B3 distributed tracing (
X-B3-TraceId/X-B3-SpanId) is propagated through both ASB message properties and HTTP headers. Use it when chasing a specific order through the logs. - Scheduling is data-driven. The
recurring_jobsDB table controls sync/export crons per store. There is no static schedule in code. cm-int-service-*services are not in this path. They handle EDI/B2B. See EDI Pipeline for that flow.
Related¶
- Camel Topology — which
cm-*service does what - Systems Architecture — the 30,000-foot view
- EDI Pipeline — B2B retail channel (the parallel flow)