OMS¶
The Order Management System — a Rails app that owns the post-checkout order lifecycle for PopSockets. Orders arrive from storefronts, move through NAV release, batching, Cirro fulfillment, and payment capture — OMS orchestrates every step.
At a Glance¶
| Purpose | Single source of truth for where an order is in the post-checkout pipeline |
| Owner | Diff Agency (built + maintained + hosted in AWS) |
| Stack | Ruby 3.4.8 · Rails 8.1.3 · MySQL · Sidekiq + Redis · Flipper · RailsEventStore |
| Repo | PopSockets/oms (originally diffagency/popsockets-si) |
| APM | Datadog (service name: ruby) |
| Deploy | Capistrano + Passenger |
Environments¶
| Env | Host | App path |
|---|---|---|
| Prod | oms.popsockets.com |
/srv/popsockets_oms-production/current (oms-production-1..4) |
| Staging | popsockets-staging.diffagency.com |
/srv/pop_sockets_oms_merge_staging-staging/current |
| Sandbox | popsockets-sandbox.diffagency.com |
/srv/pop_sockets_sandbox-sandbox/current |
OMS vs. OSOR¶
They're the same thing. "OSOR" is a legacy name for the v3 marketplace API namespace (/api/v3/osor_order/..., controller Api::V3::OsorController). It's where MuleSoft/Camel integrations call in. Don't treat it as distinct from OMS.
Integrations at a Glance¶
flowchart LR
subgraph Storefronts
SFCC[SFCC]
Shopify
Amazon[Amazon ADF]
MP[Marketplace<br/>Lazada/Jackyun]
end
subgraph Fulfillment
Cirro[Cirro 3PL]
PS[PrintStation]
BS[BatchStation]
end
subgraph ERP
NAV[NAV Dynamics]
end
subgraph Support
Smarty
Klaviyo
Arena
DD[Datadog]
end
Camel[Camel<br/>cm-* services]
ASB[(Azure<br/>Service Bus)]
OMS((OMS))
SFCC <-->|OCAPI/SCAPI<br/>OAuth2| OMS
Shopify <-->|REST + GraphQL<br/>webhooks| OMS
Amazon <-->|SP-API| OMS
MP -->|Marketplace<br/>GeneralizeOrderJob| OMS
OMS <-->|SOAP + NTLM<br/>PSCORP domain| NAV
NAV -.->|release callback<br/>via Camel| OMS
OMS <-->|APIM<br/>Basic + key| Camel
Camel <-->|Bearer| Cirro
OMS <-->|SAS<br/>raw sockets| ASB
ASB <--> BS
BS <--> PS
OMS -->|address validate| Smarty
OMS -->|transactional events| Klaviyo
OMS -->|item master sync| Arena
OMS -->|APM + StatsD| DD
style OMS fill:#ffe066,stroke:#333,stroke-width:3px
style Camel fill:#74c0fc,stroke:#333
style ASB fill:#d0bfff,stroke:#333
Responsibilities¶
OMS owns:
- Order lifecycle orchestration — ~80 statuses (see Order Status Lifecycle) driven implicitly by job outcomes. No formal state machine DSL (no AASM/statesman) — statuses are string constants on
OrderStatus, enforced loosely. - Payment capture triggering — SFCC is atomic with the fulfill call (
payment_instructions: {execute: Capture}); Shopify uses a separateCapturePaymentJob. Gateway logic lives inStore#capture_payment. - Retained order review — fraud/risk holds, address validation via Smarty, max-currency checks. Orders go
RETAINED→RETAIN_APPROVED/RETAIN_REJECTED. - Refunds, cancellations, duplicates, manual fulfillments — all through
OrderAction::*services. - Admin UI — orders, custom approvals, retained orders, error dashboard, bulk actions, Sidekiq/Flipper/EventStore access.
- Reports & reconciliation —
/order_lifecycle_reports,/dashboard/stats,/error_dashboard,/orders/bulk_reports.
OMS does not own inventory allocation (NAV does), storefront checkout (SFCC/Shopify), printing (PrintStation), packing/shipping (Cirro), carrier integration, customer notifications (Klaviyo/storefronts), returns/RMA, product catalog (Arena), tax calculation, or pricing/promotions. See What OMS Doesn't Do.
What OMS Talks To¶
| System | Direction | Mechanism |
|---|---|---|
| SFCC (Demandware) | Pull orders; push status + fulfillment + capture | OCAPI or SCAPI (Flipper sfcc_scapi_orders), OAuth2 client-credentials. Demandware::SyncOrdersJob, Demandware::Api::FulfillOrder |
| Shopify | Pull orders/products; push fulfillments/cancels/refunds | REST + GraphQL Admin API. Shopify::ShopSync::SyncOrdersJob, Shopify::CreateFulfillment. Webhook receipt via ShopifyAppAdmin::Engine |
| Amazon (ADF) | Pull orders; push ack + labels | SP-API. Amazon::SyncOrdersJob, Amazon::Api.fetch_shipping_label |
| NAV (Dynamics) | Push (SOAP); receive NAV callbacks via Camel | Savon + NTLM (domain PSCORP). app/services/nav/api.rb. 4 URLs: US, EMEA, WriteON Promo, Japan. DocTypes: 1=SalesOrder, 3=CreditMemo, 4=Product, 6=ShipmentNotice, 7=Cancellation |
| Camel | Outbound REST to APIM; Camel pushes back via Service Bus + REST | Outbound: POST CONFIG[:cirro_fulfillment_request_url] (APIM key + Basic auth). Inbound: PATCH /api/v3/osor_order/status/:order_id, POST /osor_order/fulfill_orders, POST /api/service_bus/events |
| Cirro (3PL) | Only via Camel, never direct | OMS → Camel APIM → ASB fulfillment-request-amer → Cirro. Cirro → webhook to Camel → OMS |
| BatchStation / PrintStation | Bi-directional via ASB topics | OMS publishes order.create.request / order.update.repush to int-prod-order-update-bs. Consumes int-prod-batching-update-oms. Events: Event::PrintStationOrderCreated, Event::PrintStationCancellationAcknowledged |
| Azure Service Bus | Both directions | Raw HTTPS + SAS auth in app/services/azure/service_bus/client.rb. Handwritten sockets, no Azure SDK. |
| Smarty Streets | Outbound | Address validation. app/services/smarty/validate_address.rb |
| Klaviyo | Outbound | Transactional events. app/services/klaviyo/create_event.rb |
| Arena | Outbound | Product / BOM sync. app/services/item_master/fetch_info.rb. Flipper use_prod_arena |
| Datadog | Outbound | APM + StatsD metrics + custom per-order log streams |
Gladly, Snowflake, and analytics are not integrated. The OMS DB is replicated by a separate ETL owned by another team.
Key Subsystems¶
Sidekiq Jobs (the important ones)¶
| Job | What it does |
|---|---|
Demandware::SyncOrdersJob → SyncOrderJob → GeneralizeOrderJob |
SFCC intake pipeline |
Shopify::ShopSync::SyncOrdersJob |
Shopify intake |
Amazon::SyncOrdersJob |
Amazon SP-API intake |
Marketplace::GeneralizeOrderJob |
Marketplace (Lazada, Jackyun) intake |
SplitOrderJob / SplitOrdersJob |
Splits orders by fulfillment path (printable vs finished goods vs vendor) |
Nav::CreateOrderJob |
SOAP-creates sales order (DocType 1) |
Nav::ExportOrderJob / Nav::ExportOrdersJob |
SOAP-sends shipment notice (DocType 6) |
Nav::ReleaseFromNavJob |
Processes Camel's NAV-release callback |
Nav::ExportCancellationJob |
SOAP-sends cancellation (DocType 7) |
Cirro::ExportFulfillmentRequestJob |
POSTs fulfillment request to Camel → Cirro |
Cirro::CheckInventoryJob |
Inventory verification sidecar |
PrintStation::CreateOrderJob |
Publishes order.create.request to ASB |
Orders::ConfirmFulfillmentJob |
Cirro acceptance → EXPORTED_TO_TPL |
Orders::RevertFulfillmentJob |
Cirro rejection handling |
CapturePaymentJob |
Shopify-only payment capture |
BulkActionJob |
Processes bulk-action CSVs |
UpdateStaleOrdersJob |
Marks stale orders for re-sync |
ResumeDelayedOrdersJob |
Kicks DELAYED orders back into the pipeline |
Recurring Jobs (DB-driven schedule)¶
There is no schedule.yml. Recurring job schedules live in the recurring_jobs MySQL table. Each row: job_class, store_url, warehouse_id, cron, enabled. UI at /recurring_jobs. Sidekiq-Scheduler reads RecurringJob.schedule and rebuilds on change. To change SFCC poll frequency for a store, edit the DB row, not code. recurring_job_scheduled_downtimes lets ops temporarily disable jobs without deleting cron.
Flipper Flags That Matter¶
| Flag | Meaning |
|---|---|
sfcc_scapi_orders |
Use SCAPI instead of OCAPI for SFCC pulls |
cirro_fulfillment_request |
Master switch for Cirro exports |
cirro_inventory_check |
Enable CheckInventory sidecar + OOS handling |
cirro_fulfillment_sequence |
Include TPL export sequence in Cirro payload |
fulfillment_confirmation |
New two-step flow (WAITING_FULFILLMENT_CONFIRMATION → EXPORTED_TO_TPL) vs legacy straight-to-EXPORTED_TO_TPL |
print_station_create_order |
Master switch for PrintStation batching exports |
use_prod_arena |
Point Arena calls at prod cluster in non-prod envs |
Core Models¶
Order— polymorphic-ish:has_oneofshopify_order,marketplace_order,amazon_order, etc. pointing at platform-specific details.store_typeis the discriminator.OrderStatus— 1:1 withOrder. Holds current status + per-stage booleans (generalized,nav_created,nav_exported,tpl_exported,payment_capture, etc.) and error text.after_savetriggersOrderAudit.OrderAudit— append-only log of every transition. Indexed on(status, created_at, order_id). Some statuses markedMULTIPLE_AUDITS_PERMITTEDcan repeat.OrderDetail—warehouse_id,nav_buffer_id, shipping rate data.PartialOrder— when an order splits into multiple fulfillment paths, each path gets one.LineItem/LineItemDetail/LineItemStatus/LineItemFulfillment— item-level state.LineItemAsset— printable artwork/positioning data.Demandware::Order,Shopify::ShopifyOrder,Marketplace::Order,Amazon::Order— raw platform data, kept separately from the generalizedOrder.Demandware::Client— SFCC credentials/config per instance (dev AMER, SBX3, staging APAC, India, etc.)Store— a saleable storefront withwarehouse_id,export_to_nav?,cirro?,shopify?.Warehouse— fulfillment destination. Drives NAV URL routing + Cirro region.Nav::CustomerCard— maps storefront source → NAV customer number + payment method. No SFCC entry — SFCC usescustomer.platform_iddirectly.RecurringJob/BulkActionReport— see above.
Controllers / API Namespaces¶
Api::V3::OsorController—/api/v3/osor_order/*. The primary integration surface for Camel despite the "osor" name.Api::V3::CustomersController— customer lookup.Api::V3::PrintStation::BatchesController— PrintStation batch webhooks.Api::V3::ItemMaster::ProductsController— product master sync.Api::ServiceBus::EventsController— Service Bus push-pull (Cirro fulfillment + PrintStation cancellation).Api::V1::BaseApiController— Shopify Proxy + legacy v1.- Admin:
OrdersController,CustomApprovalsController,RetainedOrdersController,ErrorDashboardController,PortalController,RecurringJobsController. - Shopify app: mounted via
ShopifyAppAdmin::Engineat/.
Order Status Lifecycle¶
OrderStatus::STATUSES holds ~80 values. They're strings, not enums. The happy-path flow for a typical SFCC/Shopify order looks like this:
stateDiagram-v2
[*] --> READY_FOR_PROCESSING: Storefront sync
READY_FOR_PROCESSING --> RETAINED: Risk/address flag
RETAINED --> RETAIN_APPROVED: CS approves
RETAINED --> RETAIN_REJECTED: CS rejects
RETAIN_REJECTED --> [*]
READY_FOR_PROCESSING --> READY_TO_CREATE_IN_NAV
RETAIN_APPROVED --> READY_TO_CREATE_IN_NAV
READY_TO_CREATE_IN_NAV --> PENDING_NAV_RELEASE: Nav::CreateOrderJob
READY_TO_CREATE_IN_NAV --> FAILED_TO_CREATE_IN_NAV: SOAP error
PENDING_NAV_RELEASE --> RELEASED_FROM_NAV: Camel callback<br/>PATCH /osor_order/status
PENDING_NAV_RELEASE --> FAILED_TO_RELEASE_FROM_NAV
RELEASED_FROM_NAV --> READY_FOR_BATCHING: Printable
RELEASED_FROM_NAV --> CIRRO_READY_FOR_FULFILLMENT: Finished goods
READY_FOR_BATCHING --> BATCHED: PrintStation<br/>batch_reference set
BATCHED --> CIRRO_READY_FOR_FULFILLMENT
CIRRO_READY_FOR_FULFILLMENT --> WAITING_FULFILLMENT_CONFIRMATION: Cirro::ExportFulfillmentRequestJob
CIRRO_READY_FOR_FULFILLMENT --> FAILED_TO_EXPORT_TO_TPL
WAITING_FULFILLMENT_CONFIRMATION --> EXPORTED_TO_TPL: Cirro acceptance<br/>(via ASB)
EXPORTED_TO_TPL --> SHIPPED: Cirro fulfill_orders<br/>(tracking)
SHIPPED --> EXPORTED_TO_NAV: Nav::ExportOrdersJob (cron)
EXPORTED_TO_NAV --> FULFILLED: Platform marked shipped
FULFILLED --> [*]
FULFILLED --> PAYMENT_CAPTURED: Shopify only<br/>(SFCC is atomic)
The main branch points are retention (risk holds), printable vs finished goods (batching fork), and new vs legacy Cirro flow (fulfillment_confirmation flag — legacy went straight to EXPORTED_TO_TPL). Cancellation, refund, and Amazon-specific states branch off most of these but aren't shown here to keep the diagram legible.
All statuses by phase¶
Intake / generalization¶
READY_FOR_PROCESSING · WAITING_FOR_RECIPE_IDS (Jackyun) · PRE_ORDER · DELAYED · AGING_ORDER · GENERALIZE_ERROR
Approval / retention¶
PENDING_APPROVALS · ALL_LINE_ITEMS_REJECTED · RETAINED · RETAIN_APPROVED · RETAIN_REJECTED
NAV creation & release¶
READY_TO_CREATE_IN_NAV → PENDING_NAV_RELEASE → RELEASED_FROM_NAV
Error branches: FAILED_TO_CREATE_IN_NAV, FAILED_TO_RELEASE_FROM_NAV
Special: STROOPWAFEL_EXCEPTION (real status — orders caught mid-lifecycle during a past refactor; not a joke)
Batching (printable only)¶
READY_FOR_BATCHING → WAITING_BATCHING_CAPACITY → BATCHED
PrintStation variants: PRINT_STATION_READY_FOR_BATCHING, _WAITING_FOR_BATCHING, _BATCHED, _FAILED_EXPORT, FAILED_TO_BATCH
Cirro export & fulfillment¶
CIRRO_READY_FOR_FULFILLMENT → CIRRO_PENDING_INVENTORY_CHECK → CIRRO_WAITING_INVENTORY → CIRRO_INVENTORY_CONFIRMED → CIRRO_WAITING_FOR_FULFILLMENT → WAITING_FULFILLMENT_CONFIRMATION → EXPORTED_TO_TPL
Error: FAILED_TO_EXPORT_TO_TPL
Shipped + post-ship¶
SHIPPED (Cirro fulfill_orders) · FULFILLED · OMS_CREATED_ORDER_FULFILLMENT · EXPORTED_TO_NAV · FAILED_TO_EXPORT_TO_NAV · PAYMENT_CAPTURED (Shopify only) · FAILED_TO_CAPTURE · FULFILLED_PAYMENT_ERROR
Cancellations & refunds¶
CS_CANCELLED · CUSTOMER_CANCELLED · OMS_CANCELLED · PLATFORM_CANCELLED · SHOPIFY_CANCELLED / _PARTIALLY_CANCELLED · SFCC_CANCELLED / _PARTIALLY_CANCELLED · EXPORTED_CANCELLATION_TO_NAV · FAILED_TO_CANCEL / _ON_NAV · CS_REFUNDED · CS_REFUNDED_ARBITRARY · EXPORTED_REFUND_TO_NAV · FAILED_TO_REFUND · FAILED_TO_EXPORT_REFUND · PROCESSED_EXTERNALLY ("we fixed it manually, stop trying") · VOIDED
Amazon-specific¶
PENDING_ACKNOWLEDGMENT · ORDER_ACKNOWLEDGED / FAILED_TO_ACKNOWLEDGE_ORDER · PENDING_SHIPPING_LABEL · AMZ_SHIPPING_LABEL_SUCCESS / _FAILED · PACKING_SLIP_GENERATED / _FAILED · AMZ_PACKING_SLIP_SUCCESS / _FAILED
Useful grouping constants¶
OrderStatus::CANCELLED_STATUSES (5 variants) · REFUNDED_STATUSES (2) · ERROR_STATUSES (~20, drives /errors) · PENDING_STATUSES (manual-kick candidates)
Authentication¶
Inbound (callers → OMS)¶
| Caller | Endpoint | Auth |
|---|---|---|
| MuleSoft / Camel | /api/v3/osor_order/* |
X-USER-TOKEN vs CONFIG[:mulesoft_api_access_token] |
| Service Bus push | /api/service_bus/events |
X-USER-TOKEN vs CONFIG[:service_bus_api_access_token] |
| PrintStation | /api/v3/print_station/batches |
HTTP Bearer vs credentials[:printstation_api_token] |
| Shopify Proxy | (v1) | HMAC-SHA256 (shopify_secret / shopify_deprecated_secret); only enforced in prod |
| Admin UI | / |
Session via ShopifyAppAdmin::User + roles (system_admin, admin, supervisor, warehouse_admin) |
| Sidekiq / Flipper / EventStore | Internal | CanAccessInternalConfigUI constraint |
Outbound (OMS → others)¶
- SFCC — OAuth2 client-credentials per
Demandware::Client - NAV — NTLM (
PSCORP\DIFFOMSstaging /PSCORP\PRODOMSprod) + cert pinning (/home/ubuntu/nav_cert/star.popsockets.com.ca-bundle.pem) - Camel — APIM subscription key + Basic auth (
cirro_username/cirro_password) - Azure Service Bus — SAS signature (handwritten HMAC, no SDK)
- Shopify — OAuth access tokens via
shopify_appgem - Amazon SP-API — LWA client credentials + STS-assumed role
- Arena — API key. Smarty — auth-id/auth-token
Rails credentials (config/credentials/<env>.yml.enc) hold all outbound creds. Master key lives on server at <shared>/config/credentials/<env>.key.
Cirro fulfillment payloads are NOT encrypted by OMS. Encryption happens in Camel when the message is published to Service Bus.
Gotchas¶
- Recurring schedule is DB-driven. No
schedule.yml. If SFCC sync stops, check/recurring_jobsUI, not configs.recurring_job_scheduled_downtimescan silently disable jobs. - Two separate "Cirro confirmations." Acceptance (ASB events →
ConfirmFulfillmentJob→EXPORTED_TO_TPL) vs Shipping (RESTfulfill_orders→SHIPPED). Camel has endpoints for both — don't route the wrong one. - Printable orders silently skip Cirro without
batch_reference.Cirro::ExportFulfillmentRequestJobguard:active_printable_items? && batch_reference.blank? → return. No error, no audit — just a log line. If an order never hits Cirro, check batching first. - Status transitions are string-based. No state machine guards — nothing stops an impossible transition. Always check current status before calling
order.order_status.update!(status: ...). OrderStatus#update!auto-creates an audit.after_save → OrderAudit.create_or_update_audit. Don't build parallel audit logging in jobs — you'll duplicate.- NAV release comes via Camel, not direct from NAV. Path: NAV → ASB
nav-order-release→cm-order-prc→cm-osor-sys→PATCH /api/v3/osor_order/status/:order_id. If NAV "isn't releasing," check Camel logs before blaming NAV or OMS. Nav::CustomerCardhas no SFCC entry. SFCC orders bypass the mapping table and usecustomer.platform_id[0..19]as the NAV billing number. OCAPI test orders frequently misscustomer_idin the payload — that breaks this.- Shopify vs SFCC payment capture are not symmetric. SFCC = one atomic call embedded in
FulfillOrder. Shopify = separateCapturePaymentJobafter platform fulfill. - Datadog may index ERROR logs at
status: info. Logstash JSON encoder quirk. Search by message content, not thestatus:field. - MySQL is IP-restricted to AKS. You cannot query prod DB from your laptop even on VPN. Use the single persistent
oms-consoletmux session on a prod box.rails runnerand new consoles are banned on remote servers. - Order names have prefixes. SFCC
00XXXX, Amazon uses PO directly. Non-prod test orders prependDEV_orOMS_. Strip cautiously. Store.urlis the join key for recurring jobs, configs, and credentials. Changing it silently orphans everything.STROOPWAFEL_EXCEPTIONis a real status, not a joke. Orders caught mid-lifecycle during a past refactor land here.- ASB client is custom TCP sockets.
Azure::ServiceBus::Client#raw_https_posthand-rolls HTTPS. If timeouts happen, look at socket-level handling, not SDK config. order_business_type: 1= "warehouse only — PopSockets ships, not Cirro." Used for GlobalE and Amazon. Not an internal category — it drives who physically handles shipment.- Bulk actions are Sidekiq jobs, not inline. 500 orders cancelled in the UI = 500
BulkActionJobs.
What OMS Doesn't Do¶
- Storefront checkout, cart, or payment authorization — SFCC/Shopify. OMS receives the finalized order.
- Inventory allocation — NAV. OMS asks and waits.
- Printing or label generation — PrintStation/BatchStation own printables; Cirro owns fulfillment labels (Amazon labels come from Amazon).
- Packing and warehouse ops — Cirro, or internal PopSockets warehouse for
order_business_type=1. - Carrier integration — Cirro picks carrier + rates.
- Customer notifications (email/SMS) — Storefronts + Klaviyo. OMS only emits specific Klaviyo events.
- CS ticketing — Not integrated with Gladly or Zendesk.
- Analytics / BI — Separate ETL team replicates the DB.
- Returns / RMA — Refunds are handled (CS UI + platform webhooks) but RMA workflow isn't here.
- Product catalog management — Arena is the master; OMS syncs a slice.
- Tax calculation — Storefronts calculate; OMS records
tax_lines. - Pricing / promotions — Storefronts own it; OMS records discount codes.
- Supply chain / purchasing — Not here.
- Amazon listing management — SP-API is read-only for orders + labels + acks.
Related¶
- B2C Order Flow — the end-to-end direct-to-consumer path through OMS
- EDI Pipeline — B2B retail (bypasses OMS; NAV + Camel handle it)
- Camel Topology — the middleware OMS talks through
- Systems Architecture — the 30,000-foot view