Skip to content
Build bf98f58

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::CreateOrderJobNav::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_statusNav::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:

  1. OMS publishes to ASB topic int-prod-order-update-bs (via PrintStation::CreateOrderService).
  2. BatchStation consumes, creates a batch, dispatches to PrintStation.
  3. After printing, BatchStation publishes to int-prod-batching-update-oms. OMS consumes and stores the order→batch mapping (batch_reference).
  4. OMS now exports to Cirro. Cirro::ExportFulfillmentRequestJob has an explicit guard: printable orders with a blank batch_reference are 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-exp publishes to ASB queue fulfillment-request-amer (or -emea, -japan for printable; -cirro for finished goods).
  • cm-fulfill-prc consumes, AES-decrypts the payload.
  • cm-fulfill-prc forwards to cm-cirro-sys, which adds the Bearer token and proxies the call to Cirro. cm-cirro-sys does 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-exp at /fulfillment/confirmation/cirro. (Retiring — new endpoints go to cm-ext-service-exp. Check which is live in prod.)
  • Webhook publishes to ASB queue fulfillment-confirmation-cirro.
  • cm-order-prc consumes (FulfillmentProcessServiceImpl) and POSTs to cm-osor-sys at /order/cirro-confirmation, which hits OMS.
  • OMS fires FulfillmentRequestAccepted (or Rejected). 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 + LineItemFulfillment records. 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::FulfillPlatformOrderPOST {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-confirmation was being sent to the wrong endpoint.
  • Printable orders hard-require a batch_reference before 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-release ASB queue → cm-order-prccm-osor-sys → OMS.
  • Payment capture is embedded in SFCC's /orderupdate call. No separate API. Easy to miss when reading the code because the payment_instructions field 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_jobs DB 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.